├── .gitignore ├── History.md ├── Makefile ├── Readme.md ├── component.json ├── examples └── reactive │ └── index.html ├── lib ├── index.js ├── proto.js └── static.js ├── package.json └── test ├── index.html ├── mocha.css ├── mocha.js ├── model.js ├── server.js └── statics.js /.gitignore: -------------------------------------------------------------------------------- 1 | components 2 | build 3 | node_modules 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.2.1 / 2015-03-16 3 | ================== 4 | 5 | * update visionmedia/superagent to v1.1.0 6 | * use `(err, res)` callback style for superagent requests 7 | 8 | 0.2.0 / 2015-03-16 9 | ================== 10 | 11 | * update component/emitter to 1.2.0 12 | * update visionmedia/superagent to 1.1.0 13 | 14 | 0.1.6 / 2014-11-13 15 | ================== 16 | 17 | * update component/each to 0.2.5 18 | * update component/emitter to 1.1.3 19 | * update visionmedia/superagent to 0.21.0 20 | 21 | 0.1.5 / 2014-07-07 22 | ================== 23 | 24 | * fix node requires 25 | 26 | 0.1.4 / 2014-06-20 27 | ================== 28 | 29 | * fix component/emitter name in package.json 30 | 31 | 0.1.3 / 2014-06-20 32 | ================== 33 | 34 | * fix component/each name in package.json 35 | * Update dependencies 36 | 37 | 0.1.2 / 2014-01-20 38 | ================== 39 | 40 | * fix: node support 41 | 42 | 0.1.1 / 2013-09-08 43 | ================== 44 | 45 | * add: request object for configuration 46 | * add model.route() (issue #47) 47 | * add simple pluralization by default 48 | * remove /all after urls 49 | * use id or _id as primary key in save callback 50 | 51 | 0.1.0 / 2013-07-01 52 | ================== 53 | 54 | * add "construct" event 55 | * change "remove()" to "destroy()" 56 | * change "removeAll()" to "destroyAll()" 57 | * removed unused json component 58 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | SRC = $(wildcard lib/*.js) 3 | 4 | build: components $(SRC) 5 | @component build --dev 6 | 7 | components: component.json 8 | @component install --dev 9 | 10 | clean: 11 | rm -fr build components template.js 12 | 13 | node_modules: package.json 14 | @npm install 15 | 16 | server: node_modules 17 | @node test/server 18 | 19 | test: build 20 | @open http://localhost:4000 21 | 22 | .PHONY: clean test -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # model 2 | 3 | W.I.P minimalistic extensible model component. 4 | 5 | ## API 6 | 7 | ### model(name) 8 | 9 | Create a new model with the given `name`. 10 | 11 | ```js 12 | var model = require('model'); 13 | var User = model('User'); 14 | ``` 15 | 16 | ### .attr(name, [meta]) 17 | 18 | Define an attribute `name` with optional `meta` data object. 19 | 20 | ```js 21 | var model = require('model'); 22 | 23 | var Post = model('Post') 24 | .attr('id') 25 | .attr('title') 26 | .attr('body') 27 | .attr('created_at') 28 | .attr('updated_at') 29 | ``` 30 | 31 | With meta data used by plugins: 32 | 33 | ```js 34 | var model = require('model'); 35 | 36 | var Post = model('Post') 37 | .attr('id', { required: true, type: 'number' }) 38 | .attr('title', { required: true, type: 'string' }) 39 | .attr('body', { required: true, type: 'string' }) 40 | .attr('created_at', { type: 'date' }) 41 | .attr('updated_at', { type: 'date' }) 42 | ``` 43 | 44 | ### .validate(fn) 45 | 46 | TODO: validation callback docs 47 | 48 | ### .use(fn) 49 | 50 | TODO: plugin docs 51 | 52 | ### .url([path]) 53 | 54 | Return base url, or url to `path`. 55 | 56 | ```js 57 | User.url() 58 | // => "/users" 59 | 60 | User.url('add') 61 | // => "/users/add" 62 | ``` 63 | 64 | ### .route(path) 65 | 66 | Set base path for urls. 67 | Note this is defaulted to `'/' + modelName.toLowerCase() + 's'` 68 | 69 | ```js 70 | User.route('/api/u') 71 | 72 | User.url() 73 | // => "/api/u" 74 | 75 | User.url('add') 76 | // => "/api/u/add" 77 | ``` 78 | 79 | ### .headers({header: value}) 80 | 81 | Sets custom headers for static and method requests on the model. 82 | 83 | ```js 84 | User.headers({ 85 | 'X-CSRF-Token': 'some token', 86 | 'X-API-Token': 'api token' 87 | }); 88 | ``` 89 | 90 | ### .ATTR() 91 | 92 | "Getter" function generated when `Model.attr(name)` is called. 93 | 94 | ```js 95 | var Post = model('Post') 96 | .attr('title') 97 | .attr('body') 98 | 99 | var post = new Post({ title: 'Cats' }); 100 | 101 | post.title() 102 | // => "Cats" 103 | ``` 104 | 105 | ### .ATTR(value) 106 | 107 | "Setter" function generated when `Model.attr(name)` is called. 108 | 109 | ```js 110 | var Post = model('Post') 111 | .attr('title') 112 | .attr('body') 113 | 114 | var post = new Post; 115 | 116 | post.title('Ferrets') 117 | post.title() 118 | // => "Ferrets" 119 | ``` 120 | 121 | - Emits "change" event with `(name, value, previousValue)`. 122 | - Emits "change ATTR" event with `(value, previousValue)`. 123 | 124 | ```js 125 | post.on('change', function(name, val, prev){ 126 | console.log('changed %s from %s to %s', name, prev, val) 127 | }) 128 | 129 | post.on('change title', function(val, prev){ 130 | console.log('changed title') 131 | }) 132 | 133 | ``` 134 | 135 | ### .isNew() 136 | 137 | Returns `true` if the model is unsaved. 138 | 139 | ### .toJSON() 140 | 141 | Return a JSON representation of the model (its attributes). 142 | 143 | ### .has(attr) 144 | 145 | Check if `attr` is non-`null`. 146 | 147 | ### .get(attr) 148 | 149 | Get `attr`'s value. 150 | 151 | ### .set(attrs) 152 | 153 | Set multiple `attrs`. 154 | 155 | ```js 156 | user.set({ name: 'Tobi', age: 2 }) 157 | ``` 158 | 159 | ### .changed([attr]) 160 | 161 | Check if the model is "dirty" and return an object 162 | of changed attributes. Optionally check a specific `attr` 163 | and return a `Boolean`. 164 | 165 | ### .error(attr, msg) 166 | 167 | Define error `msg` for `attr`. 168 | 169 | ### .isValid() 170 | 171 | Run validations and check if the model is valid. 172 | 173 | ```js 174 | user.isValid() 175 | // => false 176 | 177 | user.errors 178 | // => [{ attr: ..., message: ... }] 179 | ``` 180 | 181 | ### .url([path]) 182 | 183 | Return this model's base url or relative to `path`: 184 | 185 | ```js 186 | var user = new User({ id: 5 }); 187 | user.url('edit'); 188 | // => "/users/5/edit" 189 | ``` 190 | 191 | ### .save(fn) 192 | 193 | Save or update and invoke the given callback `fn(err)`. 194 | 195 | ```js 196 | var user = new User({ name: 'Tobi' }) 197 | 198 | user.save(function(err){ 199 | 200 | }) 201 | ``` 202 | 203 | Emits "save" when complete. 204 | 205 | ### .destroy([fn]) 206 | 207 | Destroy and invoke optional `fn(err)`. 208 | 209 | Emits "destroy" when successfully deleted. 210 | 211 | ## Links 212 | 213 | - [Plugins](https://github.com/component/model/wiki/Plugins) for model 214 | 215 | ## Testing 216 | 217 | ``` 218 | $ npm install 219 | $ make test & 220 | $ open http://localhost:3000 221 | ``` 222 | 223 | # License 224 | 225 | MIT 226 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "model", 3 | "repo": "component/model", 4 | "description": "Elegant data models", 5 | "version": "0.2.1", 6 | "keywords": [ 7 | "collection", 8 | "model", 9 | "orm", 10 | "db", 11 | "database" 12 | ], 13 | "dependencies": { 14 | "component/each": "0.2.5", 15 | "component/emitter": "1.2.0", 16 | "component/collection": "*", 17 | "visionmedia/superagent": "v1.1.0" 18 | }, 19 | "development": { 20 | "component/reactive": "0.13.0", 21 | "component/assert": "*", 22 | "component/domify": "*" 23 | }, 24 | "scripts": [ 25 | "lib/index.js", 26 | "lib/static.js", 27 | "lib/proto.js" 28 | ], 29 | "main": "lib/index.js", 30 | "license": "MIT" 31 | } -------------------------------------------------------------------------------- /examples/reactive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 |
15 |

{name}

16 |

{name} is a {age} year old {profession} with {petCount} pets.

17 |
18 | 19 | 20 | 21 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | try { 7 | var Emitter = require('emitter'); 8 | } catch (e) { 9 | var Emitter = require('component-emitter'); 10 | } 11 | 12 | var proto = require('./proto'); 13 | var statics = require('./static'); 14 | 15 | /** 16 | * Expose `createModel`. 17 | */ 18 | 19 | module.exports = createModel; 20 | 21 | /** 22 | * Create a new model constructor with the given `name`. 23 | * 24 | * @param {String} name 25 | * @return {Function} 26 | * @api public 27 | */ 28 | 29 | function createModel(name) { 30 | if ('string' != typeof name) throw new TypeError('model name required'); 31 | 32 | /** 33 | * Initialize a new model with the given `attrs`. 34 | * 35 | * @param {Object} attrs 36 | * @api public 37 | */ 38 | 39 | function model(attrs) { 40 | if (!(this instanceof model)) return new model(attrs); 41 | attrs = attrs || {}; 42 | this._callbacks = {}; 43 | this.attrs = attrs; 44 | this.dirty = attrs; 45 | this.model.emit('construct', this, attrs); 46 | } 47 | 48 | // mixin emitter 49 | 50 | Emitter(model); 51 | 52 | // statics 53 | 54 | model.modelName = name; 55 | model._base = '/' + name.toLowerCase() + 's'; 56 | model.attrs = {}; 57 | model.validators = []; 58 | model._headers = {}; 59 | for (var key in statics) model[key] = statics[key]; 60 | 61 | // prototype 62 | 63 | model.prototype = {}; 64 | model.prototype.model = model; 65 | for (var key in proto) model.prototype[key] = proto[key]; 66 | 67 | return model; 68 | } 69 | 70 | -------------------------------------------------------------------------------- /lib/proto.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | try { 7 | var Emitter = require('emitter'); 8 | var each = require('each'); 9 | } catch (e) { 10 | var Emitter = require('component-emitter'); 11 | var each = require('component-each'); 12 | } 13 | 14 | var request = require('superagent'); 15 | var noop = function(){}; 16 | 17 | /** 18 | * Mixin emitter. 19 | */ 20 | 21 | Emitter(exports); 22 | 23 | /** 24 | * Expose request for configuration 25 | */ 26 | exports.request = request; 27 | 28 | /** 29 | * Register an error `msg` on `attr`. 30 | * 31 | * @param {String} attr 32 | * @param {String} msg 33 | * @return {Object} self 34 | * @api public 35 | */ 36 | 37 | exports.error = function(attr, msg){ 38 | this.errors.push({ 39 | attr: attr, 40 | message: msg 41 | }); 42 | return this; 43 | }; 44 | 45 | /** 46 | * Check if this model is new. 47 | * 48 | * @return {Boolean} 49 | * @api public 50 | */ 51 | 52 | exports.isNew = function(){ 53 | var key = this.model.primaryKey; 54 | return ! this.has(key); 55 | }; 56 | 57 | /** 58 | * Get / set the primary key. 59 | * 60 | * @param {Mixed} val 61 | * @return {Mixed} 62 | * @api public 63 | */ 64 | 65 | exports.primary = function(val){ 66 | var key = this.model.primaryKey; 67 | if (0 == arguments.length) return this[key](); 68 | return this[key](val); 69 | }; 70 | 71 | /** 72 | * Validate the model and return a boolean. 73 | * 74 | * Example: 75 | * 76 | * user.isValid() 77 | * // => false 78 | * 79 | * user.errors 80 | * // => [{ attr: ..., message: ... }] 81 | * 82 | * @return {Boolean} 83 | * @api public 84 | */ 85 | 86 | exports.isValid = function(){ 87 | this.validate(); 88 | return 0 == this.errors.length; 89 | }; 90 | 91 | /** 92 | * Return `false` or an object 93 | * containing the "dirty" attributes. 94 | * 95 | * Optionally check for a specific `attr`. 96 | * 97 | * @param {String} [attr] 98 | * @return {Object|Boolean} 99 | * @api public 100 | */ 101 | 102 | exports.changed = function(attr){ 103 | var dirty = this.dirty; 104 | if (Object.keys(dirty).length) { 105 | if (attr) return !! dirty[attr]; 106 | return dirty; 107 | } 108 | return false; 109 | }; 110 | 111 | /** 112 | * Perform validations. 113 | * 114 | * @api private 115 | */ 116 | 117 | exports.validate = function(){ 118 | var self = this; 119 | var fns = this.model.validators; 120 | this.errors = []; 121 | each(fns, function(fn){ fn(self) }); 122 | }; 123 | 124 | /** 125 | * Destroy the model and mark it as `.destroyed` 126 | * and invoke `fn(err)`. 127 | * 128 | * Events: 129 | * 130 | * - `destroying` before deletion 131 | * - `destroy` on deletion 132 | * 133 | * @param {Function} [fn] 134 | * @api public 135 | */ 136 | 137 | exports.destroy = function(fn){ 138 | fn = fn || noop; 139 | if (this.isNew()) return fn(new Error('not saved')); 140 | var self = this; 141 | var url = this.url(); 142 | this.model.emit('destroying', this); 143 | this.emit('destroying'); 144 | this.request 145 | .del(url) 146 | .set(this.model._headers) 147 | .end(function(err, res){ 148 | if (err) return fn(err, res); 149 | self.destroyed = true; 150 | self.model.emit('destroy', self, res); 151 | self.emit('destroy'); 152 | fn(null, res); 153 | }); 154 | }; 155 | 156 | /** 157 | * Save and invoke `fn(err)`. 158 | * 159 | * Events: 160 | * 161 | * - `saving` pre-update or save, after validation 162 | * - `save` on updates and saves 163 | * 164 | * @param {Function} [fn] 165 | * @api public 166 | */ 167 | 168 | exports.save = function(fn){ 169 | if (!this.isNew()) return this.update(fn); 170 | var self = this; 171 | var url = this.model.url(); 172 | var key = this.model.primaryKey; 173 | fn = fn || noop; 174 | if (!this.isValid()) return fn(new Error('validation failed')); 175 | this.model.emit('saving', this); 176 | this.emit('saving'); 177 | this.request 178 | .post(url) 179 | .set(this.model._headers) 180 | .send(self) 181 | .end(function(err, res){ 182 | if (err) return fn(err, res); 183 | if (res.body) self.primary(res.body[key]); 184 | self.dirty = {}; 185 | self.model.emit('save', self, res); 186 | self.emit('save'); 187 | fn(null, res); 188 | }); 189 | }; 190 | 191 | /** 192 | * Update and invoke `fn(err)`. 193 | * 194 | * @param {Function} [fn] 195 | * @api private 196 | */ 197 | 198 | exports.update = function(fn){ 199 | var self = this; 200 | var url = this.url(); 201 | fn = fn || noop; 202 | if (!this.isValid()) return fn(new Error('validation failed')); 203 | this.model.emit('saving', this); 204 | this.emit('saving'); 205 | this.request 206 | .put(url) 207 | .set(this.model._headers) 208 | .send(self) 209 | .end(function(err, res){ 210 | if (err) return fn(err, res); 211 | self.dirty = {}; 212 | self.model.emit('save', self, res); 213 | self.emit('save'); 214 | fn(null, res); 215 | }); 216 | }; 217 | 218 | /** 219 | * Return a url for `path` relative to this model. 220 | * 221 | * Example: 222 | * 223 | * var user = new User({ id: 5 }); 224 | * user.url('edit'); 225 | * // => "/users/5/edit" 226 | * 227 | * @param {String} path 228 | * @return {String} 229 | * @api public 230 | */ 231 | 232 | exports.url = function(path){ 233 | var model = this.model; 234 | var url = model._base; 235 | var id = this.primary(); 236 | if (0 == arguments.length) return url + '/' + id; 237 | return url + '/' + id + '/' + path; 238 | }; 239 | 240 | /** 241 | * Set multiple `attrs`. 242 | * 243 | * @param {Object} attrs 244 | * @return {Object} self 245 | * @api public 246 | */ 247 | 248 | exports.set = function(attrs){ 249 | for (var key in attrs) { 250 | this[key](attrs[key]); 251 | } 252 | return this; 253 | }; 254 | 255 | /** 256 | * Get `attr` value. 257 | * 258 | * @param {String} attr 259 | * @return {Mixed} 260 | * @api public 261 | */ 262 | 263 | exports.get = function(attr){ 264 | return this.attrs[attr]; 265 | }; 266 | 267 | /** 268 | * Check if `attr` is present (not `null` or `undefined`). 269 | * 270 | * @param {String} attr 271 | * @return {Boolean} 272 | * @api public 273 | */ 274 | 275 | exports.has = function(attr){ 276 | return null != this.attrs[attr]; 277 | }; 278 | 279 | /** 280 | * Return the JSON representation of the model. 281 | * 282 | * @return {Object} 283 | * @api public 284 | */ 285 | 286 | exports.toJSON = function(){ 287 | return this.attrs; 288 | }; 289 | -------------------------------------------------------------------------------- /lib/static.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | try { 6 | var Collection = require('collection'); 7 | } catch (e) { 8 | var Collection = require('component-collection'); 9 | } 10 | var request = require('superagent'); 11 | var noop = function(){}; 12 | 13 | /** 14 | * Expose request for configuration 15 | */ 16 | 17 | exports.request = request; 18 | 19 | /** 20 | * Construct a url to the given `path`. 21 | * 22 | * Example: 23 | * 24 | * User.url('add') 25 | * // => "/users/add" 26 | * 27 | * @param {String} path 28 | * @return {String} 29 | * @api public 30 | */ 31 | 32 | exports.url = function(path){ 33 | var url = this._base; 34 | if (0 == arguments.length) return url; 35 | return url + '/' + path; 36 | }; 37 | 38 | /** 39 | * Set base path for urls. 40 | * Note this is defaulted to '/' + modelName.toLowerCase() + 's' 41 | * 42 | * Example: 43 | * 44 | * User.route('/api/u') 45 | * 46 | * @param {String} path 47 | * @return {Function} self 48 | * @api public 49 | */ 50 | 51 | exports.route = function(path){ 52 | this._base = path; 53 | return this; 54 | } 55 | 56 | /** 57 | * Add custom http headers to all requests. 58 | * 59 | * Example: 60 | * 61 | * User.headers({ 62 | * 'X-CSRF-Token': 'some token', 63 | * 'X-API-Token': 'api token 64 | * }); 65 | * 66 | * @param {String|Object} header(s) 67 | * @param {String} value 68 | * @return {Function} self 69 | * @api public 70 | */ 71 | 72 | exports.headers = function(headers){ 73 | for(var i in headers){ 74 | this._headers[i] = headers[i]; 75 | } 76 | return this; 77 | }; 78 | 79 | /** 80 | * Add validation `fn()`. 81 | * 82 | * @param {Function} fn 83 | * @return {Function} self 84 | * @api public 85 | */ 86 | 87 | exports.validate = function(fn){ 88 | this.validators.push(fn); 89 | return this; 90 | }; 91 | 92 | /** 93 | * Use the given plugin `fn()`. 94 | * 95 | * @param {Function} fn 96 | * @return {Function} self 97 | * @api public 98 | */ 99 | 100 | exports.use = function(fn){ 101 | fn(this); 102 | return this; 103 | }; 104 | 105 | /** 106 | * Define attr with the given `name` and `options`. 107 | * 108 | * @param {String} name 109 | * @param {Object} options 110 | * @return {Function} self 111 | * @api public 112 | */ 113 | 114 | exports.attr = function(name, options){ 115 | this.attrs[name] = options || {}; 116 | 117 | // implied pk 118 | if ('_id' == name || 'id' == name) { 119 | this.attrs[name].primaryKey = true; 120 | this.primaryKey = name; 121 | } 122 | 123 | // getter / setter method 124 | this.prototype[name] = function(val){ 125 | if (0 == arguments.length) return this.attrs[name]; 126 | var prev = this.attrs[name]; 127 | this.dirty[name] = val; 128 | this.attrs[name] = val; 129 | this.model.emit('change', this, name, val, prev); 130 | this.model.emit('change ' + name, this, val, prev); 131 | this.emit('change', name, val, prev); 132 | this.emit('change ' + name, val, prev); 133 | return this; 134 | }; 135 | 136 | return this; 137 | }; 138 | 139 | /** 140 | * Remove all and invoke `fn(err)`. 141 | * 142 | * @param {Function} [fn] 143 | * @api public 144 | */ 145 | 146 | exports.destroyAll = function(fn){ 147 | fn = fn || noop; 148 | var self = this; 149 | var url = this.url(''); 150 | this.request 151 | .del(url) 152 | .set(this._headers) 153 | .end(function(err, res){ 154 | if (err) return fn(err, null, res); 155 | fn(null, [], res); 156 | }); 157 | }; 158 | 159 | /** 160 | * Get all and invoke `fn(err, array)`. 161 | * 162 | * @param {Function} fn 163 | * @api public 164 | */ 165 | 166 | exports.all = function(fn){ 167 | var self = this; 168 | var url = this.url(''); 169 | this.request 170 | .get(url) 171 | .set(this._headers) 172 | .end(function(err, res){ 173 | if (err) return fn(err, null, res); 174 | var col = new Collection; 175 | for (var i = 0, len = res.body.length; i < len; ++i) { 176 | col.push(new self(res.body[i])); 177 | } 178 | fn(null, col, res); 179 | }); 180 | }; 181 | 182 | /** 183 | * Get `id` and invoke `fn(err, model)`. 184 | * 185 | * @param {Mixed} id 186 | * @param {Function} fn 187 | * @api public 188 | */ 189 | 190 | exports.get = function(id, fn){ 191 | var self = this; 192 | var url = this.url(id); 193 | this.request 194 | .get(url) 195 | .set(this._headers) 196 | .end(function(err, res){ 197 | if (err) return fn(err, null, res); 198 | var model = new self(res.body); 199 | fn(null, model, res); 200 | }); 201 | }; 202 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "component-model", 3 | "version": "0.3.2", 4 | "repository": "git://github.com/component/model.git", 5 | "main": "lib/index.js", 6 | "dependencies": { 7 | "component-emitter": "~1.2.0", 8 | "component-collection": "~0.0.3", 9 | "component-each": "~0.2.5", 10 | "superagent": "~1.1.0" 11 | }, 12 | "browser": { 13 | "emitter": "component-emitter", 14 | "collection": "component-collection", 15 | "each": "component-each" 16 | }, 17 | "devDependencies": { 18 | "express": "3.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Model tests 4 | 5 | 6 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | body { 3 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 4 | padding: 60px 50px; 5 | } 6 | 7 | #mocha ul, #mocha li { 8 | margin: 0; 9 | padding: 0; 10 | } 11 | 12 | #mocha ul { 13 | list-style: none; 14 | } 15 | 16 | #mocha h1, #mocha h2 { 17 | margin: 0; 18 | } 19 | 20 | #mocha h1 { 21 | margin-top: 15px; 22 | font-size: 1em; 23 | font-weight: 200; 24 | } 25 | 26 | #mocha h1 a { 27 | text-decoration: none; 28 | color: inherit; 29 | } 30 | 31 | #mocha h1 a:hover { 32 | text-decoration: underline; 33 | } 34 | 35 | #mocha .suite .suite h1 { 36 | margin-top: 0; 37 | font-size: .8em; 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | #mocha h2 { 45 | font-size: 12px; 46 | font-weight: normal; 47 | cursor: pointer; 48 | } 49 | 50 | #mocha .suite { 51 | margin-left: 15px; 52 | } 53 | 54 | #mocha .test { 55 | margin-left: 15px; 56 | } 57 | 58 | #mocha .test:hover h2::after { 59 | position: relative; 60 | top: 0; 61 | right: -10px; 62 | content: '(view source)'; 63 | font-size: 12px; 64 | font-family: arial; 65 | color: #888; 66 | } 67 | 68 | #mocha .test.pending:hover h2::after { 69 | content: '(pending)'; 70 | font-family: arial; 71 | } 72 | 73 | #mocha .test.pass.medium .duration { 74 | background: #C09853; 75 | } 76 | 77 | #mocha .test.pass.slow .duration { 78 | background: #B94A48; 79 | } 80 | 81 | #mocha .test.pass::before { 82 | content: '✓'; 83 | font-size: 12px; 84 | display: block; 85 | float: left; 86 | margin-right: 5px; 87 | color: #00d6b2; 88 | } 89 | 90 | #mocha .test.pass .duration { 91 | font-size: 9px; 92 | margin-left: 5px; 93 | padding: 2px 5px; 94 | color: white; 95 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 97 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 98 | -webkit-border-radius: 5px; 99 | -moz-border-radius: 5px; 100 | -ms-border-radius: 5px; 101 | -o-border-radius: 5px; 102 | border-radius: 5px; 103 | } 104 | 105 | #mocha .test.pass.fast .duration { 106 | display: none; 107 | } 108 | 109 | #mocha .test.pending { 110 | color: #0b97c4; 111 | } 112 | 113 | #mocha .test.pending::before { 114 | content: '◦'; 115 | color: #0b97c4; 116 | } 117 | 118 | #mocha .test.fail { 119 | color: #c00; 120 | } 121 | 122 | #mocha .test.fail pre { 123 | color: black; 124 | } 125 | 126 | #mocha .test.fail::before { 127 | content: '✖'; 128 | font-size: 12px; 129 | display: block; 130 | float: left; 131 | margin-right: 5px; 132 | color: #c00; 133 | } 134 | 135 | #mocha .test pre.error { 136 | color: #c00; 137 | } 138 | 139 | #mocha .test pre { 140 | display: inline-block; 141 | font: 12px/1.5 monaco, monospace; 142 | margin: 5px; 143 | padding: 15px; 144 | border: 1px solid #eee; 145 | border-bottom-color: #ddd; 146 | -webkit-border-radius: 3px; 147 | -webkit-box-shadow: 0 1px 3px #eee; 148 | } 149 | 150 | #report.pass .test.fail { 151 | display: none; 152 | } 153 | 154 | #report.fail .test.pass { 155 | display: none; 156 | } 157 | 158 | #error { 159 | color: #c00; 160 | font-size: 1.5 em; 161 | font-weight: 100; 162 | letter-spacing: 1px; 163 | } 164 | 165 | #stats { 166 | position: fixed; 167 | top: 15px; 168 | right: 10px; 169 | font-size: 12px; 170 | margin: 0; 171 | color: #888; 172 | } 173 | 174 | #stats .progress { 175 | float: right; 176 | padding-top: 0; 177 | } 178 | 179 | #stats em { 180 | color: black; 181 | } 182 | 183 | #stats a { 184 | text-decoration: none; 185 | color: inherit; 186 | } 187 | 188 | #stats a:hover { 189 | border-bottom: 1px solid #eee; 190 | } 191 | 192 | #stats li { 193 | display: inline-block; 194 | margin: 0 5px; 195 | list-style: none; 196 | padding-top: 11px; 197 | } 198 | 199 | code .comment { color: #ddd } 200 | code .init { color: #2F6FAD } 201 | code .string { color: #5890AD } 202 | code .keyword { color: #8A6343 } 203 | code .number { color: #2F6FAD } 204 | -------------------------------------------------------------------------------- /test/mocha.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | 4 | // CommonJS require() 5 | 6 | function require(p){ 7 | var path = require.resolve(p) 8 | , mod = require.modules[path]; 9 | if (!mod) throw new Error('failed to require "' + p + '"'); 10 | if (!mod.exports) { 11 | mod.exports = {}; 12 | mod.call(mod.exports, mod, mod.exports, require.relative(path)); 13 | } 14 | return mod.exports; 15 | } 16 | 17 | require.modules = {}; 18 | 19 | require.resolve = function (path){ 20 | var orig = path 21 | , reg = path + '.js' 22 | , index = path + '/index.js'; 23 | return require.modules[reg] && reg 24 | || require.modules[index] && index 25 | || orig; 26 | }; 27 | 28 | require.register = function (path, fn){ 29 | require.modules[path] = fn; 30 | }; 31 | 32 | require.relative = function (parent) { 33 | return function(p){ 34 | if ('.' != p.charAt(0)) return require(p); 35 | 36 | var path = parent.split('/') 37 | , segs = p.split('/'); 38 | path.pop(); 39 | 40 | for (var i = 0; i < segs.length; i++) { 41 | var seg = segs[i]; 42 | if ('..' == seg) path.pop(); 43 | else if ('.' != seg) path.push(seg); 44 | } 45 | 46 | return require(path.join('/')); 47 | }; 48 | }; 49 | 50 | 51 | require.register("browser/debug.js", function(module, exports, require){ 52 | 53 | module.exports = function(type){ 54 | return function(){ 55 | 56 | } 57 | }; 58 | }); // module: browser/debug.js 59 | 60 | require.register("browser/diff.js", function(module, exports, require){ 61 | 62 | }); // module: browser/diff.js 63 | 64 | require.register("browser/events.js", function(module, exports, require){ 65 | 66 | /** 67 | * Module exports. 68 | */ 69 | 70 | exports.EventEmitter = EventEmitter; 71 | 72 | /** 73 | * Check if `obj` is an array. 74 | */ 75 | 76 | function isArray(obj) { 77 | return '[object Array]' == {}.toString.call(obj); 78 | } 79 | 80 | /** 81 | * Event emitter constructor. 82 | * 83 | * @api public 84 | */ 85 | 86 | function EventEmitter(){}; 87 | 88 | /** 89 | * Adds a listener. 90 | * 91 | * @api public 92 | */ 93 | 94 | EventEmitter.prototype.on = function (name, fn) { 95 | if (!this.$events) { 96 | this.$events = {}; 97 | } 98 | 99 | if (!this.$events[name]) { 100 | this.$events[name] = fn; 101 | } else if (isArray(this.$events[name])) { 102 | this.$events[name].push(fn); 103 | } else { 104 | this.$events[name] = [this.$events[name], fn]; 105 | } 106 | 107 | return this; 108 | }; 109 | 110 | EventEmitter.prototype.addListener = EventEmitter.prototype.on; 111 | 112 | /** 113 | * Adds a volatile listener. 114 | * 115 | * @api public 116 | */ 117 | 118 | EventEmitter.prototype.once = function (name, fn) { 119 | var self = this; 120 | 121 | function on () { 122 | self.removeListener(name, on); 123 | fn.apply(this, arguments); 124 | }; 125 | 126 | on.listener = fn; 127 | this.on(name, on); 128 | 129 | return this; 130 | }; 131 | 132 | /** 133 | * Removes a listener. 134 | * 135 | * @api public 136 | */ 137 | 138 | EventEmitter.prototype.removeListener = function (name, fn) { 139 | if (this.$events && this.$events[name]) { 140 | var list = this.$events[name]; 141 | 142 | if (isArray(list)) { 143 | var pos = -1; 144 | 145 | for (var i = 0, l = list.length; i < l; i++) { 146 | if (list[i] === fn || (list[i].listener && list[i].listener === fn)) { 147 | pos = i; 148 | break; 149 | } 150 | } 151 | 152 | if (pos < 0) { 153 | return this; 154 | } 155 | 156 | list.splice(pos, 1); 157 | 158 | if (!list.length) { 159 | delete this.$events[name]; 160 | } 161 | } else if (list === fn || (list.listener && list.listener === fn)) { 162 | delete this.$events[name]; 163 | } 164 | } 165 | 166 | return this; 167 | }; 168 | 169 | /** 170 | * Removes all listeners for an event. 171 | * 172 | * @api public 173 | */ 174 | 175 | EventEmitter.prototype.removeAllListeners = function (name) { 176 | if (name === undefined) { 177 | this.$events = {}; 178 | return this; 179 | } 180 | 181 | if (this.$events && this.$events[name]) { 182 | this.$events[name] = null; 183 | } 184 | 185 | return this; 186 | }; 187 | 188 | /** 189 | * Gets all listeners for a certain event. 190 | * 191 | * @api public 192 | */ 193 | 194 | EventEmitter.prototype.listeners = function (name) { 195 | if (!this.$events) { 196 | this.$events = {}; 197 | } 198 | 199 | if (!this.$events[name]) { 200 | this.$events[name] = []; 201 | } 202 | 203 | if (!isArray(this.$events[name])) { 204 | this.$events[name] = [this.$events[name]]; 205 | } 206 | 207 | return this.$events[name]; 208 | }; 209 | 210 | /** 211 | * Emits an event. 212 | * 213 | * @api public 214 | */ 215 | 216 | EventEmitter.prototype.emit = function (name) { 217 | if (!this.$events) { 218 | return false; 219 | } 220 | 221 | var handler = this.$events[name]; 222 | 223 | if (!handler) { 224 | return false; 225 | } 226 | 227 | var args = [].slice.call(arguments, 1); 228 | 229 | if ('function' == typeof handler) { 230 | handler.apply(this, args); 231 | } else if (isArray(handler)) { 232 | var listeners = handler.slice(); 233 | 234 | for (var i = 0, l = listeners.length; i < l; i++) { 235 | listeners[i].apply(this, args); 236 | } 237 | } else { 238 | return false; 239 | } 240 | 241 | return true; 242 | }; 243 | }); // module: browser/events.js 244 | 245 | require.register("browser/fs.js", function(module, exports, require){ 246 | 247 | }); // module: browser/fs.js 248 | 249 | require.register("browser/path.js", function(module, exports, require){ 250 | 251 | }); // module: browser/path.js 252 | 253 | require.register("browser/progress.js", function(module, exports, require){ 254 | 255 | /** 256 | * Expose `Progress`. 257 | */ 258 | 259 | module.exports = Progress; 260 | 261 | /** 262 | * Initialize a new `Progress` indicator. 263 | */ 264 | 265 | function Progress() { 266 | this.percent = 0; 267 | this.size(0); 268 | this.fontSize(11); 269 | this.font('helvetica, arial, sans-serif'); 270 | } 271 | 272 | /** 273 | * Set progress size to `n`. 274 | * 275 | * @param {Number} n 276 | * @return {Progress} for chaining 277 | * @api public 278 | */ 279 | 280 | Progress.prototype.size = function(n){ 281 | this._size = n; 282 | return this; 283 | }; 284 | 285 | /** 286 | * Set text to `str`. 287 | * 288 | * @param {String} str 289 | * @return {Progress} for chaining 290 | * @api public 291 | */ 292 | 293 | Progress.prototype.text = function(str){ 294 | this._text = str; 295 | return this; 296 | }; 297 | 298 | /** 299 | * Set font size to `n`. 300 | * 301 | * @param {Number} n 302 | * @return {Progress} for chaining 303 | * @api public 304 | */ 305 | 306 | Progress.prototype.fontSize = function(n){ 307 | this._fontSize = n; 308 | return this; 309 | }; 310 | 311 | /** 312 | * Set font `family`. 313 | * 314 | * @param {String} family 315 | * @return {Progress} for chaining 316 | */ 317 | 318 | Progress.prototype.font = function(family){ 319 | this._font = family; 320 | return this; 321 | }; 322 | 323 | /** 324 | * Update percentage to `n`. 325 | * 326 | * @param {Number} n 327 | * @return {Progress} for chaining 328 | */ 329 | 330 | Progress.prototype.update = function(n){ 331 | this.percent = n; 332 | return this; 333 | }; 334 | 335 | /** 336 | * Draw on `ctx`. 337 | * 338 | * @param {CanvasRenderingContext2d} ctx 339 | * @return {Progress} for chaining 340 | */ 341 | 342 | Progress.prototype.draw = function(ctx){ 343 | var percent = Math.min(this.percent, 100) 344 | , size = this._size 345 | , half = size / 2 346 | , x = half 347 | , y = half 348 | , rad = half - 1 349 | , fontSize = this._fontSize; 350 | 351 | ctx.font = fontSize + 'px ' + this._font; 352 | 353 | var angle = Math.PI * 2 * (percent / 100); 354 | ctx.clearRect(0, 0, size, size); 355 | 356 | // outer circle 357 | ctx.strokeStyle = '#9f9f9f'; 358 | ctx.beginPath(); 359 | ctx.arc(x, y, rad, 0, angle, false); 360 | ctx.stroke(); 361 | 362 | // inner circle 363 | ctx.strokeStyle = '#eee'; 364 | ctx.beginPath(); 365 | ctx.arc(x, y, rad - 1, 0, angle, true); 366 | ctx.stroke(); 367 | 368 | // text 369 | var text = this._text || (percent | 0) + '%' 370 | , w = ctx.measureText(text).width; 371 | 372 | ctx.fillText( 373 | text 374 | , x - w / 2 + 1 375 | , y + fontSize / 2 - 1); 376 | 377 | return this; 378 | }; 379 | 380 | }); // module: browser/progress.js 381 | 382 | require.register("browser/tty.js", function(module, exports, require){ 383 | 384 | exports.isatty = function(){ 385 | return true; 386 | }; 387 | 388 | exports.getWindowSize = function(){ 389 | return [window.innerHeight, window.innerWidth]; 390 | }; 391 | }); // module: browser/tty.js 392 | 393 | require.register("context.js", function(module, exports, require){ 394 | 395 | /** 396 | * Expose `Context`. 397 | */ 398 | 399 | module.exports = Context; 400 | 401 | /** 402 | * Initialize a new `Context`. 403 | * 404 | * @api private 405 | */ 406 | 407 | function Context(){} 408 | 409 | /** 410 | * Set or get the context `Runnable` to `runnable`. 411 | * 412 | * @param {Runnable} runnable 413 | * @return {Context} 414 | * @api private 415 | */ 416 | 417 | Context.prototype.runnable = function(runnable){ 418 | if (0 == arguments.length) return this._runnable; 419 | this.test = this._runnable = runnable; 420 | return this; 421 | }; 422 | 423 | /** 424 | * Set test timeout `ms`. 425 | * 426 | * @param {Number} ms 427 | * @return {Context} self 428 | * @api private 429 | */ 430 | 431 | Context.prototype.timeout = function(ms){ 432 | this.runnable().timeout(ms); 433 | return this; 434 | }; 435 | 436 | /** 437 | * Set test slowness threshold `ms`. 438 | * 439 | * @param {Number} ms 440 | * @return {Context} self 441 | * @api private 442 | */ 443 | 444 | Context.prototype.slow = function(ms){ 445 | this.runnable().slow(ms); 446 | return this; 447 | }; 448 | 449 | /** 450 | * Inspect the context void of `._runnable`. 451 | * 452 | * @return {String} 453 | * @api private 454 | */ 455 | 456 | Context.prototype.inspect = function(){ 457 | return JSON.stringify(this, function(key, val){ 458 | if ('_runnable' == key) return; 459 | if ('test' == key) return; 460 | return val; 461 | }, 2); 462 | }; 463 | 464 | }); // module: context.js 465 | 466 | require.register("hook.js", function(module, exports, require){ 467 | 468 | /** 469 | * Module dependencies. 470 | */ 471 | 472 | var Runnable = require('./runnable'); 473 | 474 | /** 475 | * Expose `Hook`. 476 | */ 477 | 478 | module.exports = Hook; 479 | 480 | /** 481 | * Initialize a new `Hook` with the given `title` and callback `fn`. 482 | * 483 | * @param {String} title 484 | * @param {Function} fn 485 | * @api private 486 | */ 487 | 488 | function Hook(title, fn) { 489 | Runnable.call(this, title, fn); 490 | this.type = 'hook'; 491 | } 492 | 493 | /** 494 | * Inherit from `Runnable.prototype`. 495 | */ 496 | 497 | Hook.prototype = new Runnable; 498 | Hook.prototype.constructor = Hook; 499 | 500 | 501 | /** 502 | * Get or set the test `err`. 503 | * 504 | * @param {Error} err 505 | * @return {Error} 506 | * @api public 507 | */ 508 | 509 | Hook.prototype.error = function(err){ 510 | if (0 == arguments.length) { 511 | var err = this._error; 512 | this._error = null; 513 | return err; 514 | } 515 | 516 | this._error = err; 517 | }; 518 | 519 | 520 | }); // module: hook.js 521 | 522 | require.register("interfaces/bdd.js", function(module, exports, require){ 523 | 524 | /** 525 | * Module dependencies. 526 | */ 527 | 528 | var Suite = require('../suite') 529 | , Test = require('../test'); 530 | 531 | /** 532 | * BDD-style interface: 533 | * 534 | * describe('Array', function(){ 535 | * describe('#indexOf()', function(){ 536 | * it('should return -1 when not present', function(){ 537 | * 538 | * }); 539 | * 540 | * it('should return the index when present', function(){ 541 | * 542 | * }); 543 | * }); 544 | * }); 545 | * 546 | */ 547 | 548 | module.exports = function(suite){ 549 | var suites = [suite]; 550 | 551 | suite.on('pre-require', function(context, file, mocha){ 552 | 553 | /** 554 | * Execute before running tests. 555 | */ 556 | 557 | context.before = function(fn){ 558 | suites[0].beforeAll(fn); 559 | }; 560 | 561 | /** 562 | * Execute after running tests. 563 | */ 564 | 565 | context.after = function(fn){ 566 | suites[0].afterAll(fn); 567 | }; 568 | 569 | /** 570 | * Execute before each test case. 571 | */ 572 | 573 | context.beforeEach = function(fn){ 574 | suites[0].beforeEach(fn); 575 | }; 576 | 577 | /** 578 | * Execute after each test case. 579 | */ 580 | 581 | context.afterEach = function(fn){ 582 | suites[0].afterEach(fn); 583 | }; 584 | 585 | /** 586 | * Describe a "suite" with the given `title` 587 | * and callback `fn` containing nested suites 588 | * and/or tests. 589 | */ 590 | 591 | context.describe = context.context = function(title, fn){ 592 | var suite = Suite.create(suites[0], title); 593 | suites.unshift(suite); 594 | fn(); 595 | suites.shift(); 596 | return suite; 597 | }; 598 | 599 | /** 600 | * Pending describe. 601 | */ 602 | 603 | context.xdescribe = 604 | context.xcontext = 605 | context.describe.skip = function(title, fn){ 606 | var suite = Suite.create(suites[0], title); 607 | suite.pending = true; 608 | suites.unshift(suite); 609 | fn(); 610 | suites.shift(); 611 | }; 612 | 613 | /** 614 | * Exclusive suite. 615 | */ 616 | 617 | context.describe.only = function(title, fn){ 618 | var suite = context.describe(title, fn); 619 | mocha.grep(suite.fullTitle()); 620 | }; 621 | 622 | /** 623 | * Describe a specification or test-case 624 | * with the given `title` and callback `fn` 625 | * acting as a thunk. 626 | */ 627 | 628 | context.it = context.specify = function(title, fn){ 629 | var suite = suites[0]; 630 | if (suite.pending) var fn = null; 631 | var test = new Test(title, fn); 632 | suite.addTest(test); 633 | return test; 634 | }; 635 | 636 | /** 637 | * Exclusive test-case. 638 | */ 639 | 640 | context.it.only = function(title, fn){ 641 | var test = context.it(title, fn); 642 | mocha.grep(test.fullTitle()); 643 | }; 644 | 645 | /** 646 | * Pending test case. 647 | */ 648 | 649 | context.xit = 650 | context.xspecify = 651 | context.it.skip = function(title){ 652 | context.it(title); 653 | }; 654 | }); 655 | }; 656 | 657 | }); // module: interfaces/bdd.js 658 | 659 | require.register("interfaces/exports.js", function(module, exports, require){ 660 | 661 | /** 662 | * Module dependencies. 663 | */ 664 | 665 | var Suite = require('../suite') 666 | , Test = require('../test'); 667 | 668 | /** 669 | * TDD-style interface: 670 | * 671 | * exports.Array = { 672 | * '#indexOf()': { 673 | * 'should return -1 when the value is not present': function(){ 674 | * 675 | * }, 676 | * 677 | * 'should return the correct index when the value is present': function(){ 678 | * 679 | * } 680 | * } 681 | * }; 682 | * 683 | */ 684 | 685 | module.exports = function(suite){ 686 | var suites = [suite]; 687 | 688 | suite.on('require', visit); 689 | 690 | function visit(obj) { 691 | var suite; 692 | for (var key in obj) { 693 | if ('function' == typeof obj[key]) { 694 | var fn = obj[key]; 695 | switch (key) { 696 | case 'before': 697 | suites[0].beforeAll(fn); 698 | break; 699 | case 'after': 700 | suites[0].afterAll(fn); 701 | break; 702 | case 'beforeEach': 703 | suites[0].beforeEach(fn); 704 | break; 705 | case 'afterEach': 706 | suites[0].afterEach(fn); 707 | break; 708 | default: 709 | suites[0].addTest(new Test(key, fn)); 710 | } 711 | } else { 712 | var suite = Suite.create(suites[0], key); 713 | suites.unshift(suite); 714 | visit(obj[key]); 715 | suites.shift(); 716 | } 717 | } 718 | } 719 | }; 720 | }); // module: interfaces/exports.js 721 | 722 | require.register("interfaces/index.js", function(module, exports, require){ 723 | 724 | exports.bdd = require('./bdd'); 725 | exports.tdd = require('./tdd'); 726 | exports.qunit = require('./qunit'); 727 | exports.exports = require('./exports'); 728 | 729 | }); // module: interfaces/index.js 730 | 731 | require.register("interfaces/qunit.js", function(module, exports, require){ 732 | 733 | /** 734 | * Module dependencies. 735 | */ 736 | 737 | var Suite = require('../suite') 738 | , Test = require('../test'); 739 | 740 | /** 741 | * QUnit-style interface: 742 | * 743 | * suite('Array'); 744 | * 745 | * test('#length', function(){ 746 | * var arr = [1,2,3]; 747 | * ok(arr.length == 3); 748 | * }); 749 | * 750 | * test('#indexOf()', function(){ 751 | * var arr = [1,2,3]; 752 | * ok(arr.indexOf(1) == 0); 753 | * ok(arr.indexOf(2) == 1); 754 | * ok(arr.indexOf(3) == 2); 755 | * }); 756 | * 757 | * suite('String'); 758 | * 759 | * test('#length', function(){ 760 | * ok('foo'.length == 3); 761 | * }); 762 | * 763 | */ 764 | 765 | module.exports = function(suite){ 766 | var suites = [suite]; 767 | 768 | suite.on('pre-require', function(context){ 769 | 770 | /** 771 | * Execute before running tests. 772 | */ 773 | 774 | context.before = function(fn){ 775 | suites[0].beforeAll(fn); 776 | }; 777 | 778 | /** 779 | * Execute after running tests. 780 | */ 781 | 782 | context.after = function(fn){ 783 | suites[0].afterAll(fn); 784 | }; 785 | 786 | /** 787 | * Execute before each test case. 788 | */ 789 | 790 | context.beforeEach = function(fn){ 791 | suites[0].beforeEach(fn); 792 | }; 793 | 794 | /** 795 | * Execute after each test case. 796 | */ 797 | 798 | context.afterEach = function(fn){ 799 | suites[0].afterEach(fn); 800 | }; 801 | 802 | /** 803 | * Describe a "suite" with the given `title`. 804 | */ 805 | 806 | context.suite = function(title){ 807 | if (suites.length > 1) suites.shift(); 808 | var suite = Suite.create(suites[0], title); 809 | suites.unshift(suite); 810 | }; 811 | 812 | /** 813 | * Describe a specification or test-case 814 | * with the given `title` and callback `fn` 815 | * acting as a thunk. 816 | */ 817 | 818 | context.test = function(title, fn){ 819 | suites[0].addTest(new Test(title, fn)); 820 | }; 821 | }); 822 | }; 823 | 824 | }); // module: interfaces/qunit.js 825 | 826 | require.register("interfaces/tdd.js", function(module, exports, require){ 827 | 828 | /** 829 | * Module dependencies. 830 | */ 831 | 832 | var Suite = require('../suite') 833 | , Test = require('../test'); 834 | 835 | /** 836 | * TDD-style interface: 837 | * 838 | * suite('Array', function(){ 839 | * suite('#indexOf()', function(){ 840 | * suiteSetup(function(){ 841 | * 842 | * }); 843 | * 844 | * test('should return -1 when not present', function(){ 845 | * 846 | * }); 847 | * 848 | * test('should return the index when present', function(){ 849 | * 850 | * }); 851 | * 852 | * suiteTeardown(function(){ 853 | * 854 | * }); 855 | * }); 856 | * }); 857 | * 858 | */ 859 | 860 | module.exports = function(suite){ 861 | var suites = [suite]; 862 | 863 | suite.on('pre-require', function(context, file, mocha){ 864 | 865 | /** 866 | * Execute before each test case. 867 | */ 868 | 869 | context.setup = function(fn){ 870 | suites[0].beforeEach(fn); 871 | }; 872 | 873 | /** 874 | * Execute after each test case. 875 | */ 876 | 877 | context.teardown = function(fn){ 878 | suites[0].afterEach(fn); 879 | }; 880 | 881 | /** 882 | * Execute before the suite. 883 | */ 884 | 885 | context.suiteSetup = function(fn){ 886 | suites[0].beforeAll(fn); 887 | }; 888 | 889 | /** 890 | * Execute after the suite. 891 | */ 892 | 893 | context.suiteTeardown = function(fn){ 894 | suites[0].afterAll(fn); 895 | }; 896 | 897 | /** 898 | * Describe a "suite" with the given `title` 899 | * and callback `fn` containing nested suites 900 | * and/or tests. 901 | */ 902 | 903 | context.suite = function(title, fn){ 904 | var suite = Suite.create(suites[0], title); 905 | suites.unshift(suite); 906 | fn(); 907 | suites.shift(); 908 | return suite; 909 | }; 910 | 911 | /** 912 | * Exclusive test-case. 913 | */ 914 | 915 | context.suite.only = function(title, fn){ 916 | var suite = context.suite(title, fn); 917 | mocha.grep(suite.fullTitle()); 918 | }; 919 | 920 | /** 921 | * Describe a specification or test-case 922 | * with the given `title` and callback `fn` 923 | * acting as a thunk. 924 | */ 925 | 926 | context.test = function(title, fn){ 927 | var test = new Test(title, fn); 928 | suites[0].addTest(test); 929 | return test; 930 | }; 931 | 932 | /** 933 | * Exclusive test-case. 934 | */ 935 | 936 | context.test.only = function(title, fn){ 937 | var test = context.test(title, fn); 938 | mocha.grep(test.fullTitle()); 939 | }; 940 | }); 941 | }; 942 | 943 | }); // module: interfaces/tdd.js 944 | 945 | require.register("mocha.js", function(module, exports, require){ 946 | /*! 947 | * mocha 948 | * Copyright(c) 2011 TJ Holowaychuk 949 | * MIT Licensed 950 | */ 951 | 952 | /** 953 | * Module dependencies. 954 | */ 955 | 956 | var path = require('browser/path') 957 | , utils = require('./utils'); 958 | 959 | /** 960 | * Expose `Mocha`. 961 | */ 962 | 963 | exports = module.exports = Mocha; 964 | 965 | /** 966 | * Expose internals. 967 | */ 968 | 969 | exports.utils = utils; 970 | exports.interfaces = require('./interfaces'); 971 | exports.reporters = require('./reporters'); 972 | exports.Runnable = require('./runnable'); 973 | exports.Context = require('./context'); 974 | exports.Runner = require('./runner'); 975 | exports.Suite = require('./suite'); 976 | exports.Hook = require('./hook'); 977 | exports.Test = require('./test'); 978 | 979 | /** 980 | * Return image `name` path. 981 | * 982 | * @param {String} name 983 | * @return {String} 984 | * @api private 985 | */ 986 | 987 | function image(name) { 988 | return __dirname + '/../images/' + name + '.png'; 989 | } 990 | 991 | /** 992 | * Setup mocha with `options`. 993 | * 994 | * Options: 995 | * 996 | * - `ui` name "bdd", "tdd", "exports" etc 997 | * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` 998 | * - `globals` array of accepted globals 999 | * - `timeout` timeout in milliseconds 1000 | * - `slow` milliseconds to wait before considering a test slow 1001 | * - `ignoreLeaks` ignore global leaks 1002 | * - `grep` string or regexp to filter tests with 1003 | * 1004 | * @param {Object} options 1005 | * @api public 1006 | */ 1007 | 1008 | function Mocha(options) { 1009 | options = options || {}; 1010 | this.files = []; 1011 | this.options = options; 1012 | this.grep(options.grep); 1013 | this.suite = new exports.Suite('', new exports.Context); 1014 | this.ui(options.ui); 1015 | this.reporter(options.reporter); 1016 | if (options.timeout) this.timeout(options.timeout); 1017 | if (options.slow) this.slow(options.slow); 1018 | } 1019 | 1020 | /** 1021 | * Add test `file`. 1022 | * 1023 | * @param {String} file 1024 | * @api public 1025 | */ 1026 | 1027 | Mocha.prototype.addFile = function(file){ 1028 | this.files.push(file); 1029 | return this; 1030 | }; 1031 | 1032 | /** 1033 | * Set reporter to `reporter`, defaults to "dot". 1034 | * 1035 | * @param {String|Function} reporter name of a reporter or a reporter constructor 1036 | * @api public 1037 | */ 1038 | 1039 | Mocha.prototype.reporter = function(reporter){ 1040 | if ('function' == typeof reporter) { 1041 | this._reporter = reporter; 1042 | } else { 1043 | reporter = reporter || 'dot'; 1044 | try { 1045 | this._reporter = require('./reporters/' + reporter); 1046 | } catch (err) { 1047 | this._reporter = require(reporter); 1048 | } 1049 | if (!this._reporter) throw new Error('invalid reporter "' + reporter + '"'); 1050 | } 1051 | return this; 1052 | }; 1053 | 1054 | /** 1055 | * Set test UI `name`, defaults to "bdd". 1056 | * 1057 | * @param {String} bdd 1058 | * @api public 1059 | */ 1060 | 1061 | Mocha.prototype.ui = function(name){ 1062 | name = name || 'bdd'; 1063 | this._ui = exports.interfaces[name]; 1064 | if (!this._ui) throw new Error('invalid interface "' + name + '"'); 1065 | this._ui = this._ui(this.suite); 1066 | return this; 1067 | }; 1068 | 1069 | /** 1070 | * Load registered files. 1071 | * 1072 | * @api private 1073 | */ 1074 | 1075 | Mocha.prototype.loadFiles = function(fn){ 1076 | var self = this; 1077 | var suite = this.suite; 1078 | var pending = this.files.length; 1079 | this.files.forEach(function(file){ 1080 | file = path.resolve(file); 1081 | suite.emit('pre-require', global, file, self); 1082 | suite.emit('require', require(file), file, self); 1083 | suite.emit('post-require', global, file, self); 1084 | --pending || (fn && fn()); 1085 | }); 1086 | }; 1087 | 1088 | /** 1089 | * Enable growl support. 1090 | * 1091 | * @api private 1092 | */ 1093 | 1094 | Mocha.prototype._growl = function(runner, reporter) { 1095 | var notify = require('growl'); 1096 | 1097 | runner.on('end', function(){ 1098 | var stats = reporter.stats; 1099 | if (stats.failures) { 1100 | var msg = stats.failures + ' of ' + runner.total + ' tests failed'; 1101 | notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); 1102 | } else { 1103 | notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { 1104 | name: 'mocha' 1105 | , title: 'Passed' 1106 | , image: image('ok') 1107 | }); 1108 | } 1109 | }); 1110 | }; 1111 | 1112 | /** 1113 | * Add regexp to grep, if `re` is a string it is escaped. 1114 | * 1115 | * @param {RegExp|String} re 1116 | * @return {Mocha} 1117 | * @api public 1118 | */ 1119 | 1120 | Mocha.prototype.grep = function(re){ 1121 | this.options.grep = 'string' == typeof re 1122 | ? new RegExp(utils.escapeRegexp(re)) 1123 | : re; 1124 | return this; 1125 | }; 1126 | 1127 | /** 1128 | * Invert `.grep()` matches. 1129 | * 1130 | * @return {Mocha} 1131 | * @api public 1132 | */ 1133 | 1134 | Mocha.prototype.invert = function(){ 1135 | this.options.invert = true; 1136 | return this; 1137 | }; 1138 | 1139 | /** 1140 | * Ignore global leaks. 1141 | * 1142 | * @return {Mocha} 1143 | * @api public 1144 | */ 1145 | 1146 | Mocha.prototype.ignoreLeaks = function(){ 1147 | this.options.ignoreLeaks = true; 1148 | return this; 1149 | }; 1150 | 1151 | /** 1152 | * Enable global leak checking. 1153 | * 1154 | * @return {Mocha} 1155 | * @api public 1156 | */ 1157 | 1158 | Mocha.prototype.checkLeaks = function(){ 1159 | this.options.ignoreLeaks = false; 1160 | return this; 1161 | }; 1162 | 1163 | /** 1164 | * Enable growl support. 1165 | * 1166 | * @return {Mocha} 1167 | * @api public 1168 | */ 1169 | 1170 | Mocha.prototype.growl = function(){ 1171 | this.options.growl = true; 1172 | return this; 1173 | }; 1174 | 1175 | /** 1176 | * Ignore `globals` array or string. 1177 | * 1178 | * @param {Array|String} globals 1179 | * @return {Mocha} 1180 | * @api public 1181 | */ 1182 | 1183 | Mocha.prototype.globals = function(globals){ 1184 | this.options.globals = (this.options.globals || []).concat(globals); 1185 | return this; 1186 | }; 1187 | 1188 | /** 1189 | * Set the timeout in milliseconds. 1190 | * 1191 | * @param {Number} timeout 1192 | * @return {Mocha} 1193 | * @api public 1194 | */ 1195 | 1196 | Mocha.prototype.timeout = function(timeout){ 1197 | this.suite.timeout(timeout); 1198 | return this; 1199 | }; 1200 | 1201 | /** 1202 | * Set slowness threshold in milliseconds. 1203 | * 1204 | * @param {Number} slow 1205 | * @return {Mocha} 1206 | * @api public 1207 | */ 1208 | 1209 | Mocha.prototype.slow = function(slow){ 1210 | this.suite.slow(slow); 1211 | return this; 1212 | }; 1213 | 1214 | /** 1215 | * Run tests and invoke `fn()` when complete. 1216 | * 1217 | * @param {Function} fn 1218 | * @return {Runner} 1219 | * @api public 1220 | */ 1221 | 1222 | Mocha.prototype.run = function(fn){ 1223 | if (this.files.length) this.loadFiles(); 1224 | var suite = this.suite; 1225 | var options = this.options; 1226 | var runner = new exports.Runner(suite); 1227 | var reporter = new this._reporter(runner); 1228 | runner.ignoreLeaks = options.ignoreLeaks; 1229 | if (options.grep) runner.grep(options.grep, options.invert); 1230 | if (options.globals) runner.globals(options.globals); 1231 | if (options.growl) this._growl(runner, reporter); 1232 | return runner.run(fn); 1233 | }; 1234 | 1235 | }); // module: mocha.js 1236 | 1237 | require.register("ms.js", function(module, exports, require){ 1238 | 1239 | /** 1240 | * Helpers. 1241 | */ 1242 | 1243 | var s = 1000; 1244 | var m = s * 60; 1245 | var h = m * 60; 1246 | var d = h * 24; 1247 | 1248 | /** 1249 | * Parse or format the given `val`. 1250 | * 1251 | * @param {String|Number} val 1252 | * @return {String|Number} 1253 | * @api public 1254 | */ 1255 | 1256 | module.exports = function(val){ 1257 | if ('string' == typeof val) return parse(val); 1258 | return format(val); 1259 | } 1260 | 1261 | /** 1262 | * Parse the given `str` and return milliseconds. 1263 | * 1264 | * @param {String} str 1265 | * @return {Number} 1266 | * @api private 1267 | */ 1268 | 1269 | function parse(str) { 1270 | var m = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str); 1271 | if (!m) return; 1272 | var n = parseFloat(m[1]); 1273 | var type = (m[2] || 'ms').toLowerCase(); 1274 | switch (type) { 1275 | case 'years': 1276 | case 'year': 1277 | case 'y': 1278 | return n * 31557600000; 1279 | case 'days': 1280 | case 'day': 1281 | case 'd': 1282 | return n * 86400000; 1283 | case 'hours': 1284 | case 'hour': 1285 | case 'h': 1286 | return n * 3600000; 1287 | case 'minutes': 1288 | case 'minute': 1289 | case 'm': 1290 | return n * 60000; 1291 | case 'seconds': 1292 | case 'second': 1293 | case 's': 1294 | return n * 1000; 1295 | case 'ms': 1296 | return n; 1297 | } 1298 | } 1299 | 1300 | /** 1301 | * Format the given `ms`. 1302 | * 1303 | * @param {Number} ms 1304 | * @return {String} 1305 | * @api public 1306 | */ 1307 | 1308 | function format(ms) { 1309 | if (ms == d) return (ms / d) + ' day'; 1310 | if (ms > d) return (ms / d) + ' days'; 1311 | if (ms == h) return (ms / h) + ' hour'; 1312 | if (ms > h) return (ms / h) + ' hours'; 1313 | if (ms == m) return (ms / m) + ' minute'; 1314 | if (ms > m) return (ms / m) + ' minutes'; 1315 | if (ms == s) return (ms / s) + ' second'; 1316 | if (ms > s) return (ms / s) + ' seconds'; 1317 | return ms + ' ms'; 1318 | } 1319 | }); // module: ms.js 1320 | 1321 | require.register("reporters/base.js", function(module, exports, require){ 1322 | 1323 | /** 1324 | * Module dependencies. 1325 | */ 1326 | 1327 | var tty = require('browser/tty') 1328 | , diff = require('browser/diff') 1329 | , ms = require('../ms'); 1330 | 1331 | /** 1332 | * Save timer references to avoid Sinon interfering (see GH-237). 1333 | */ 1334 | 1335 | var Date = global.Date 1336 | , setTimeout = global.setTimeout 1337 | , setInterval = global.setInterval 1338 | , clearTimeout = global.clearTimeout 1339 | , clearInterval = global.clearInterval; 1340 | 1341 | /** 1342 | * Check if both stdio streams are associated with a tty. 1343 | */ 1344 | 1345 | var isatty = tty.isatty(1) && tty.isatty(2); 1346 | 1347 | /** 1348 | * Expose `Base`. 1349 | */ 1350 | 1351 | exports = module.exports = Base; 1352 | 1353 | /** 1354 | * Enable coloring by default. 1355 | */ 1356 | 1357 | exports.useColors = isatty; 1358 | 1359 | /** 1360 | * Default color map. 1361 | */ 1362 | 1363 | exports.colors = { 1364 | 'pass': 90 1365 | , 'fail': 31 1366 | , 'bright pass': 92 1367 | , 'bright fail': 91 1368 | , 'bright yellow': 93 1369 | , 'pending': 36 1370 | , 'suite': 0 1371 | , 'error title': 0 1372 | , 'error message': 31 1373 | , 'error stack': 90 1374 | , 'checkmark': 32 1375 | , 'fast': 90 1376 | , 'medium': 33 1377 | , 'slow': 31 1378 | , 'green': 32 1379 | , 'light': 90 1380 | , 'diff gutter': 90 1381 | , 'diff added': 42 1382 | , 'diff removed': 41 1383 | }; 1384 | 1385 | /** 1386 | * Color `str` with the given `type`, 1387 | * allowing colors to be disabled, 1388 | * as well as user-defined color 1389 | * schemes. 1390 | * 1391 | * @param {String} type 1392 | * @param {String} str 1393 | * @return {String} 1394 | * @api private 1395 | */ 1396 | 1397 | var color = exports.color = function(type, str) { 1398 | if (!exports.useColors) return str; 1399 | return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; 1400 | }; 1401 | 1402 | /** 1403 | * Expose term window size, with some 1404 | * defaults for when stderr is not a tty. 1405 | */ 1406 | 1407 | exports.window = { 1408 | width: isatty 1409 | ? process.stdout.getWindowSize 1410 | ? process.stdout.getWindowSize(1)[0] 1411 | : tty.getWindowSize()[1] 1412 | : 75 1413 | }; 1414 | 1415 | /** 1416 | * Expose some basic cursor interactions 1417 | * that are common among reporters. 1418 | */ 1419 | 1420 | exports.cursor = { 1421 | hide: function(){ 1422 | process.stdout.write('\u001b[?25l'); 1423 | }, 1424 | 1425 | show: function(){ 1426 | process.stdout.write('\u001b[?25h'); 1427 | }, 1428 | 1429 | deleteLine: function(){ 1430 | process.stdout.write('\u001b[2K'); 1431 | }, 1432 | 1433 | beginningOfLine: function(){ 1434 | process.stdout.write('\u001b[0G'); 1435 | }, 1436 | 1437 | CR: function(){ 1438 | exports.cursor.deleteLine(); 1439 | exports.cursor.beginningOfLine(); 1440 | } 1441 | }; 1442 | 1443 | /** 1444 | * Outut the given `failures` as a list. 1445 | * 1446 | * @param {Array} failures 1447 | * @api public 1448 | */ 1449 | 1450 | exports.list = function(failures){ 1451 | console.error(); 1452 | failures.forEach(function(test, i){ 1453 | // format 1454 | var fmt = color('error title', ' %s) %s:\n') 1455 | + color('error message', ' %s') 1456 | + color('error stack', '\n%s\n'); 1457 | 1458 | // msg 1459 | var err = test.err 1460 | , message = err.message || '' 1461 | , stack = err.stack || message 1462 | , index = stack.indexOf(message) + message.length 1463 | , msg = stack.slice(0, index) 1464 | , actual = err.actual 1465 | , expected = err.expected 1466 | , escape = true; 1467 | 1468 | // explicitly show diff 1469 | if (err.showDiff) { 1470 | escape = false; 1471 | err.actual = actual = JSON.stringify(actual, null, 2); 1472 | err.expected = expected = JSON.stringify(expected, null, 2); 1473 | } 1474 | 1475 | // actual / expected diff 1476 | if ('string' == typeof actual && 'string' == typeof expected) { 1477 | var len = Math.max(actual.length, expected.length); 1478 | 1479 | if (len < 20) msg = errorDiff(err, 'Chars', escape); 1480 | else msg = errorDiff(err, 'Words', escape); 1481 | 1482 | // linenos 1483 | var lines = msg.split('\n'); 1484 | if (lines.length > 4) { 1485 | var width = String(lines.length).length; 1486 | msg = lines.map(function(str, i){ 1487 | return pad(++i, width) + ' |' + ' ' + str; 1488 | }).join('\n'); 1489 | } 1490 | 1491 | // legend 1492 | msg = '\n' 1493 | + color('diff removed', 'actual') 1494 | + ' ' 1495 | + color('diff added', 'expected') 1496 | + '\n\n' 1497 | + msg 1498 | + '\n'; 1499 | 1500 | // indent 1501 | msg = msg.replace(/^/gm, ' '); 1502 | 1503 | fmt = color('error title', ' %s) %s:\n%s') 1504 | + color('error stack', '\n%s\n'); 1505 | } 1506 | 1507 | // indent stack trace without msg 1508 | stack = stack.slice(index ? index + 1 : index) 1509 | .replace(/^/gm, ' '); 1510 | 1511 | console.error(fmt, (i + 1), test.fullTitle(), msg, stack); 1512 | }); 1513 | }; 1514 | 1515 | /** 1516 | * Initialize a new `Base` reporter. 1517 | * 1518 | * All other reporters generally 1519 | * inherit from this reporter, providing 1520 | * stats such as test duration, number 1521 | * of tests passed / failed etc. 1522 | * 1523 | * @param {Runner} runner 1524 | * @api public 1525 | */ 1526 | 1527 | function Base(runner) { 1528 | var self = this 1529 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } 1530 | , failures = this.failures = []; 1531 | 1532 | if (!runner) return; 1533 | this.runner = runner; 1534 | 1535 | runner.on('start', function(){ 1536 | stats.start = new Date; 1537 | }); 1538 | 1539 | runner.on('suite', function(suite){ 1540 | stats.suites = stats.suites || 0; 1541 | suite.root || stats.suites++; 1542 | }); 1543 | 1544 | runner.on('test end', function(test){ 1545 | stats.tests = stats.tests || 0; 1546 | stats.tests++; 1547 | }); 1548 | 1549 | runner.on('pass', function(test){ 1550 | stats.passes = stats.passes || 0; 1551 | 1552 | var medium = test.slow() / 2; 1553 | test.speed = test.duration > test.slow() 1554 | ? 'slow' 1555 | : test.duration > medium 1556 | ? 'medium' 1557 | : 'fast'; 1558 | 1559 | stats.passes++; 1560 | }); 1561 | 1562 | runner.on('fail', function(test, err){ 1563 | stats.failures = stats.failures || 0; 1564 | stats.failures++; 1565 | test.err = err; 1566 | failures.push(test); 1567 | }); 1568 | 1569 | runner.on('end', function(){ 1570 | stats.end = new Date; 1571 | stats.duration = new Date - stats.start; 1572 | }); 1573 | 1574 | runner.on('pending', function(){ 1575 | stats.pending++; 1576 | }); 1577 | } 1578 | 1579 | /** 1580 | * Output common epilogue used by many of 1581 | * the bundled reporters. 1582 | * 1583 | * @api public 1584 | */ 1585 | 1586 | Base.prototype.epilogue = function(){ 1587 | var stats = this.stats 1588 | , fmt 1589 | , tests; 1590 | 1591 | console.log(); 1592 | 1593 | function pluralize(n) { 1594 | return 1 == n ? 'test' : 'tests'; 1595 | } 1596 | 1597 | // failure 1598 | if (stats.failures) { 1599 | fmt = color('bright fail', ' ✖') 1600 | + color('fail', ' %d of %d %s failed') 1601 | + color('light', ':') 1602 | 1603 | console.error(fmt, 1604 | stats.failures, 1605 | this.runner.total, 1606 | pluralize(this.runner.total)); 1607 | 1608 | Base.list(this.failures); 1609 | console.error(); 1610 | return; 1611 | } 1612 | 1613 | // pass 1614 | fmt = color('bright pass', ' ✔') 1615 | + color('green', ' %d %s complete') 1616 | + color('light', ' (%s)'); 1617 | 1618 | console.log(fmt, 1619 | stats.tests || 0, 1620 | pluralize(stats.tests), 1621 | ms(stats.duration)); 1622 | 1623 | // pending 1624 | if (stats.pending) { 1625 | fmt = color('pending', ' •') 1626 | + color('pending', ' %d %s pending'); 1627 | 1628 | console.log(fmt, stats.pending, pluralize(stats.pending)); 1629 | } 1630 | 1631 | console.log(); 1632 | }; 1633 | 1634 | /** 1635 | * Pad the given `str` to `len`. 1636 | * 1637 | * @param {String} str 1638 | * @param {String} len 1639 | * @return {String} 1640 | * @api private 1641 | */ 1642 | 1643 | function pad(str, len) { 1644 | str = String(str); 1645 | return Array(len - str.length + 1).join(' ') + str; 1646 | } 1647 | 1648 | /** 1649 | * Return a character diff for `err`. 1650 | * 1651 | * @param {Error} err 1652 | * @return {String} 1653 | * @api private 1654 | */ 1655 | 1656 | function errorDiff(err, type, escape) { 1657 | return diff['diff' + type](err.actual, err.expected).map(function(str){ 1658 | if (escape) { 1659 | str.value = str.value 1660 | .replace(/\t/g, '') 1661 | .replace(/\r/g, '') 1662 | .replace(/\n/g, '\n'); 1663 | } 1664 | if (str.added) return colorLines('diff added', str.value); 1665 | if (str.removed) return colorLines('diff removed', str.value); 1666 | return str.value; 1667 | }).join(''); 1668 | } 1669 | 1670 | /** 1671 | * Color lines for `str`, using the color `name`. 1672 | * 1673 | * @param {String} name 1674 | * @param {String} str 1675 | * @return {String} 1676 | * @api private 1677 | */ 1678 | 1679 | function colorLines(name, str) { 1680 | return str.split('\n').map(function(str){ 1681 | return color(name, str); 1682 | }).join('\n'); 1683 | } 1684 | 1685 | }); // module: reporters/base.js 1686 | 1687 | require.register("reporters/doc.js", function(module, exports, require){ 1688 | 1689 | /** 1690 | * Module dependencies. 1691 | */ 1692 | 1693 | var Base = require('./base') 1694 | , utils = require('../utils'); 1695 | 1696 | /** 1697 | * Expose `Doc`. 1698 | */ 1699 | 1700 | exports = module.exports = Doc; 1701 | 1702 | /** 1703 | * Initialize a new `Doc` reporter. 1704 | * 1705 | * @param {Runner} runner 1706 | * @api public 1707 | */ 1708 | 1709 | function Doc(runner) { 1710 | Base.call(this, runner); 1711 | 1712 | var self = this 1713 | , stats = this.stats 1714 | , total = runner.total 1715 | , indents = 2; 1716 | 1717 | function indent() { 1718 | return Array(indents).join(' '); 1719 | } 1720 | 1721 | runner.on('suite', function(suite){ 1722 | if (suite.root) return; 1723 | ++indents; 1724 | console.log('%s
', indent()); 1725 | ++indents; 1726 | console.log('%s

%s

', indent(), suite.title); 1727 | console.log('%s
', indent()); 1728 | }); 1729 | 1730 | runner.on('suite end', function(suite){ 1731 | if (suite.root) return; 1732 | console.log('%s
', indent()); 1733 | --indents; 1734 | console.log('%s
', indent()); 1735 | --indents; 1736 | }); 1737 | 1738 | runner.on('pass', function(test){ 1739 | console.log('%s
%s
', indent(), test.title); 1740 | var code = utils.escape(utils.clean(test.fn.toString())); 1741 | console.log('%s
%s
', indent(), code); 1742 | }); 1743 | } 1744 | 1745 | }); // module: reporters/doc.js 1746 | 1747 | require.register("reporters/dot.js", function(module, exports, require){ 1748 | 1749 | /** 1750 | * Module dependencies. 1751 | */ 1752 | 1753 | var Base = require('./base') 1754 | , color = Base.color; 1755 | 1756 | /** 1757 | * Expose `Dot`. 1758 | */ 1759 | 1760 | exports = module.exports = Dot; 1761 | 1762 | /** 1763 | * Initialize a new `Dot` matrix test reporter. 1764 | * 1765 | * @param {Runner} runner 1766 | * @api public 1767 | */ 1768 | 1769 | function Dot(runner) { 1770 | Base.call(this, runner); 1771 | 1772 | var self = this 1773 | , stats = this.stats 1774 | , width = Base.window.width * .75 | 0 1775 | , c = '․' 1776 | , n = 0; 1777 | 1778 | runner.on('start', function(){ 1779 | process.stdout.write('\n '); 1780 | }); 1781 | 1782 | runner.on('pending', function(test){ 1783 | process.stdout.write(color('pending', c)); 1784 | }); 1785 | 1786 | runner.on('pass', function(test){ 1787 | if (++n % width == 0) process.stdout.write('\n '); 1788 | if ('slow' == test.speed) { 1789 | process.stdout.write(color('bright yellow', c)); 1790 | } else { 1791 | process.stdout.write(color(test.speed, c)); 1792 | } 1793 | }); 1794 | 1795 | runner.on('fail', function(test, err){ 1796 | if (++n % width == 0) process.stdout.write('\n '); 1797 | process.stdout.write(color('fail', c)); 1798 | }); 1799 | 1800 | runner.on('end', function(){ 1801 | console.log(); 1802 | self.epilogue(); 1803 | }); 1804 | } 1805 | 1806 | /** 1807 | * Inherit from `Base.prototype`. 1808 | */ 1809 | 1810 | Dot.prototype = new Base; 1811 | Dot.prototype.constructor = Dot; 1812 | 1813 | }); // module: reporters/dot.js 1814 | 1815 | require.register("reporters/html-cov.js", function(module, exports, require){ 1816 | 1817 | /** 1818 | * Module dependencies. 1819 | */ 1820 | 1821 | var JSONCov = require('./json-cov') 1822 | , fs = require('browser/fs'); 1823 | 1824 | /** 1825 | * Expose `HTMLCov`. 1826 | */ 1827 | 1828 | exports = module.exports = HTMLCov; 1829 | 1830 | /** 1831 | * Initialize a new `JsCoverage` reporter. 1832 | * 1833 | * @param {Runner} runner 1834 | * @api public 1835 | */ 1836 | 1837 | function HTMLCov(runner) { 1838 | var jade = require('jade') 1839 | , file = __dirname + '/templates/coverage.jade' 1840 | , str = fs.readFileSync(file, 'utf8') 1841 | , fn = jade.compile(str, { filename: file }) 1842 | , self = this; 1843 | 1844 | JSONCov.call(this, runner, false); 1845 | 1846 | runner.on('end', function(){ 1847 | process.stdout.write(fn({ 1848 | cov: self.cov 1849 | , coverageClass: coverageClass 1850 | })); 1851 | }); 1852 | } 1853 | 1854 | /** 1855 | * Return coverage class for `n`. 1856 | * 1857 | * @return {String} 1858 | * @api private 1859 | */ 1860 | 1861 | function coverageClass(n) { 1862 | if (n >= 75) return 'high'; 1863 | if (n >= 50) return 'medium'; 1864 | if (n >= 25) return 'low'; 1865 | return 'terrible'; 1866 | } 1867 | }); // module: reporters/html-cov.js 1868 | 1869 | require.register("reporters/html.js", function(module, exports, require){ 1870 | 1871 | /** 1872 | * Module dependencies. 1873 | */ 1874 | 1875 | var Base = require('./base') 1876 | , utils = require('../utils') 1877 | , Progress = require('../browser/progress') 1878 | , escape = utils.escape; 1879 | 1880 | /** 1881 | * Save timer references to avoid Sinon interfering (see GH-237). 1882 | */ 1883 | 1884 | var Date = global.Date 1885 | , setTimeout = global.setTimeout 1886 | , setInterval = global.setInterval 1887 | , clearTimeout = global.clearTimeout 1888 | , clearInterval = global.clearInterval; 1889 | 1890 | /** 1891 | * Expose `Doc`. 1892 | */ 1893 | 1894 | exports = module.exports = HTML; 1895 | 1896 | /** 1897 | * Stats template. 1898 | */ 1899 | 1900 | var statsTemplate = '
    ' 1901 | + '
  • ' 1902 | + '
  • passes: 0
  • ' 1903 | + '
  • failures: 0
  • ' 1904 | + '
  • duration: 0s
  • ' 1905 | + '
'; 1906 | 1907 | /** 1908 | * Initialize a new `Doc` reporter. 1909 | * 1910 | * @param {Runner} runner 1911 | * @api public 1912 | */ 1913 | 1914 | function HTML(runner, root) { 1915 | Base.call(this, runner); 1916 | 1917 | var self = this 1918 | , stats = this.stats 1919 | , total = runner.total 1920 | , stat = fragment(statsTemplate) 1921 | , items = stat.getElementsByTagName('li') 1922 | , passes = items[1].getElementsByTagName('em')[0] 1923 | , passesLink = items[1].getElementsByTagName('a')[0] 1924 | , failures = items[2].getElementsByTagName('em')[0] 1925 | , failuresLink = items[2].getElementsByTagName('a')[0] 1926 | , duration = items[3].getElementsByTagName('em')[0] 1927 | , canvas = stat.getElementsByTagName('canvas')[0] 1928 | , report = fragment('
    ') 1929 | , stack = [report] 1930 | , progress 1931 | , ctx 1932 | 1933 | root = root || document.getElementById('mocha'); 1934 | 1935 | if (canvas.getContext) { 1936 | var ratio = window.devicePixelRatio || 1; 1937 | canvas.style.width = canvas.width; 1938 | canvas.style.height = canvas.height; 1939 | canvas.width *= ratio; 1940 | canvas.height *= ratio; 1941 | ctx = canvas.getContext('2d'); 1942 | ctx.scale(ratio, ratio); 1943 | progress = new Progress; 1944 | } 1945 | 1946 | if (!root) return error('#mocha div missing, add it to your document'); 1947 | 1948 | // pass toggle 1949 | on(passesLink, 'click', function(){ 1950 | unhide(); 1951 | var name = /pass/.test(report.className) ? '' : ' pass'; 1952 | report.className = report.className.replace(/fail|pass/g, '') + name; 1953 | if (report.className.trim()) hideSuitesWithout('test pass'); 1954 | }); 1955 | 1956 | // failure toggle 1957 | on(failuresLink, 'click', function(){ 1958 | unhide(); 1959 | var name = /fail/.test(report.className) ? '' : ' fail'; 1960 | report.className = report.className.replace(/fail|pass/g, '') + name; 1961 | if (report.className.trim()) hideSuitesWithout('test fail'); 1962 | }); 1963 | 1964 | root.appendChild(stat); 1965 | root.appendChild(report); 1966 | 1967 | if (progress) progress.size(40); 1968 | 1969 | runner.on('suite', function(suite){ 1970 | if (suite.root) return; 1971 | 1972 | // suite 1973 | var url = '?grep=' + encodeURIComponent(suite.fullTitle()); 1974 | var el = fragment('
  • %s

  • ', url, escape(suite.title)); 1975 | 1976 | // container 1977 | stack[0].appendChild(el); 1978 | stack.unshift(document.createElement('ul')); 1979 | el.appendChild(stack[0]); 1980 | }); 1981 | 1982 | runner.on('suite end', function(suite){ 1983 | if (suite.root) return; 1984 | stack.shift(); 1985 | }); 1986 | 1987 | runner.on('fail', function(test, err){ 1988 | if ('hook' == test.type || err.uncaught) runner.emit('test end', test); 1989 | }); 1990 | 1991 | runner.on('test end', function(test){ 1992 | window.scrollTo(0, document.body.scrollHeight); 1993 | 1994 | // TODO: add to stats 1995 | var percent = stats.tests / total * 100 | 0; 1996 | if (progress) progress.update(percent).draw(ctx); 1997 | 1998 | // update stats 1999 | var ms = new Date - stats.start; 2000 | text(passes, stats.passes); 2001 | text(failures, stats.failures); 2002 | text(duration, (ms / 1000).toFixed(2)); 2003 | 2004 | // test 2005 | if ('passed' == test.state) { 2006 | var el = fragment('
  • %e%ems

  • ', test.speed, test.title, test.duration); 2007 | } else if (test.pending) { 2008 | var el = fragment('
  • %e

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

  • ', test.title); 2011 | var str = test.err.stack || test.err.toString(); 2012 | 2013 | // FF / Opera do not add the message 2014 | if (!~str.indexOf(test.err.message)) { 2015 | str = test.err.message + '\n' + str; 2016 | } 2017 | 2018 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we 2019 | // check for the result of the stringifying. 2020 | if ('[object Error]' == str) str = test.err.message; 2021 | 2022 | // Safari doesn't give you a stack. Let's at least provide a source line. 2023 | if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { 2024 | str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; 2025 | } 2026 | 2027 | el.appendChild(fragment('
    %e
    ', str)); 2028 | } 2029 | 2030 | // toggle code 2031 | // TODO: defer 2032 | if (!test.pending) { 2033 | var h2 = el.getElementsByTagName('h2')[0]; 2034 | 2035 | on(h2, 'click', function(){ 2036 | pre.style.display = 'none' == pre.style.display 2037 | ? 'inline-block' 2038 | : 'none'; 2039 | }); 2040 | 2041 | var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); 2042 | el.appendChild(pre); 2043 | pre.style.display = 'none'; 2044 | } 2045 | 2046 | stack[0].appendChild(el); 2047 | }); 2048 | } 2049 | 2050 | /** 2051 | * Display error `msg`. 2052 | */ 2053 | 2054 | function error(msg) { 2055 | document.body.appendChild(fragment('
    %s
    ', msg)); 2056 | } 2057 | 2058 | /** 2059 | * Return a DOM fragment from `html`. 2060 | */ 2061 | 2062 | function fragment(html) { 2063 | var args = arguments 2064 | , div = document.createElement('div') 2065 | , i = 1; 2066 | 2067 | div.innerHTML = html.replace(/%([se])/g, function(_, type){ 2068 | switch (type) { 2069 | case 's': return String(args[i++]); 2070 | case 'e': return escape(args[i++]); 2071 | } 2072 | }); 2073 | 2074 | return div.firstChild; 2075 | } 2076 | 2077 | /** 2078 | * Check for suites that do not have elements 2079 | * with `classname`, and hide them. 2080 | */ 2081 | 2082 | function hideSuitesWithout(classname) { 2083 | var suites = document.getElementsByClassName('suite'); 2084 | for (var i = 0; i < suites.length; i++) { 2085 | var els = suites[i].getElementsByClassName(classname); 2086 | if (0 == els.length) suites[i].className += ' hidden'; 2087 | } 2088 | } 2089 | 2090 | /** 2091 | * Unhide .hidden suites. 2092 | */ 2093 | 2094 | function unhide() { 2095 | var els = document.getElementsByClassName('suite hidden'); 2096 | for (var i = 0; i < els.length; ++i) { 2097 | els[i].className = els[i].className.replace('suite hidden', 'suite'); 2098 | } 2099 | } 2100 | 2101 | /** 2102 | * Set `el` text to `str`. 2103 | */ 2104 | 2105 | function text(el, str) { 2106 | if (el.textContent) { 2107 | el.textContent = str; 2108 | } else { 2109 | el.innerText = str; 2110 | } 2111 | } 2112 | 2113 | /** 2114 | * Listen on `event` with callback `fn`. 2115 | */ 2116 | 2117 | function on(el, event, fn) { 2118 | if (el.addEventListener) { 2119 | el.addEventListener(event, fn, false); 2120 | } else { 2121 | el.attachEvent('on' + event, fn); 2122 | } 2123 | } 2124 | 2125 | }); // module: reporters/html.js 2126 | 2127 | require.register("reporters/index.js", function(module, exports, require){ 2128 | 2129 | exports.Base = require('./base'); 2130 | exports.Dot = require('./dot'); 2131 | exports.Doc = require('./doc'); 2132 | exports.TAP = require('./tap'); 2133 | exports.JSON = require('./json'); 2134 | exports.HTML = require('./html'); 2135 | exports.List = require('./list'); 2136 | exports.Min = require('./min'); 2137 | exports.Spec = require('./spec'); 2138 | exports.Nyan = require('./nyan'); 2139 | exports.XUnit = require('./xunit'); 2140 | exports.Markdown = require('./markdown'); 2141 | exports.Progress = require('./progress'); 2142 | exports.Landing = require('./landing'); 2143 | exports.JSONCov = require('./json-cov'); 2144 | exports.HTMLCov = require('./html-cov'); 2145 | exports.JSONStream = require('./json-stream'); 2146 | exports.Teamcity = require('./teamcity'); 2147 | 2148 | }); // module: reporters/index.js 2149 | 2150 | require.register("reporters/json-cov.js", function(module, exports, require){ 2151 | 2152 | /** 2153 | * Module dependencies. 2154 | */ 2155 | 2156 | var Base = require('./base'); 2157 | 2158 | /** 2159 | * Expose `JSONCov`. 2160 | */ 2161 | 2162 | exports = module.exports = JSONCov; 2163 | 2164 | /** 2165 | * Initialize a new `JsCoverage` reporter. 2166 | * 2167 | * @param {Runner} runner 2168 | * @param {Boolean} output 2169 | * @api public 2170 | */ 2171 | 2172 | function JSONCov(runner, output) { 2173 | var self = this 2174 | , output = 1 == arguments.length ? true : output; 2175 | 2176 | Base.call(this, runner); 2177 | 2178 | var tests = [] 2179 | , failures = [] 2180 | , passes = []; 2181 | 2182 | runner.on('test end', function(test){ 2183 | tests.push(test); 2184 | }); 2185 | 2186 | runner.on('pass', function(test){ 2187 | passes.push(test); 2188 | }); 2189 | 2190 | runner.on('fail', function(test){ 2191 | failures.push(test); 2192 | }); 2193 | 2194 | runner.on('end', function(){ 2195 | var cov = global._$jscoverage || {}; 2196 | var result = self.cov = map(cov); 2197 | result.stats = self.stats; 2198 | result.tests = tests.map(clean); 2199 | result.failures = failures.map(clean); 2200 | result.passes = passes.map(clean); 2201 | if (!output) return; 2202 | process.stdout.write(JSON.stringify(result, null, 2 )); 2203 | }); 2204 | } 2205 | 2206 | /** 2207 | * Map jscoverage data to a JSON structure 2208 | * suitable for reporting. 2209 | * 2210 | * @param {Object} cov 2211 | * @return {Object} 2212 | * @api private 2213 | */ 2214 | 2215 | function map(cov) { 2216 | var ret = { 2217 | instrumentation: 'node-jscoverage' 2218 | , sloc: 0 2219 | , hits: 0 2220 | , misses: 0 2221 | , coverage: 0 2222 | , files: [] 2223 | }; 2224 | 2225 | for (var filename in cov) { 2226 | var data = coverage(filename, cov[filename]); 2227 | ret.files.push(data); 2228 | ret.hits += data.hits; 2229 | ret.misses += data.misses; 2230 | ret.sloc += data.sloc; 2231 | } 2232 | 2233 | if (ret.sloc > 0) { 2234 | ret.coverage = (ret.hits / ret.sloc) * 100; 2235 | } 2236 | 2237 | return ret; 2238 | }; 2239 | 2240 | /** 2241 | * Map jscoverage data for a single source file 2242 | * to a JSON structure suitable for reporting. 2243 | * 2244 | * @param {String} filename name of the source file 2245 | * @param {Object} data jscoverage coverage data 2246 | * @return {Object} 2247 | * @api private 2248 | */ 2249 | 2250 | function coverage(filename, data) { 2251 | var ret = { 2252 | filename: filename, 2253 | coverage: 0, 2254 | hits: 0, 2255 | misses: 0, 2256 | sloc: 0, 2257 | source: {} 2258 | }; 2259 | 2260 | data.source.forEach(function(line, num){ 2261 | num++; 2262 | 2263 | if (data[num] === 0) { 2264 | ret.misses++; 2265 | ret.sloc++; 2266 | } else if (data[num] !== undefined) { 2267 | ret.hits++; 2268 | ret.sloc++; 2269 | } 2270 | 2271 | ret.source[num] = { 2272 | source: line 2273 | , coverage: data[num] === undefined 2274 | ? '' 2275 | : data[num] 2276 | }; 2277 | }); 2278 | 2279 | ret.coverage = ret.hits / ret.sloc * 100; 2280 | 2281 | return ret; 2282 | } 2283 | 2284 | /** 2285 | * Return a plain-object representation of `test` 2286 | * free of cyclic properties etc. 2287 | * 2288 | * @param {Object} test 2289 | * @return {Object} 2290 | * @api private 2291 | */ 2292 | 2293 | function clean(test) { 2294 | return { 2295 | title: test.title 2296 | , fullTitle: test.fullTitle() 2297 | , duration: test.duration 2298 | } 2299 | } 2300 | 2301 | }); // module: reporters/json-cov.js 2302 | 2303 | require.register("reporters/json-stream.js", function(module, exports, require){ 2304 | 2305 | /** 2306 | * Module dependencies. 2307 | */ 2308 | 2309 | var Base = require('./base') 2310 | , color = Base.color; 2311 | 2312 | /** 2313 | * Expose `List`. 2314 | */ 2315 | 2316 | exports = module.exports = List; 2317 | 2318 | /** 2319 | * Initialize a new `List` test reporter. 2320 | * 2321 | * @param {Runner} runner 2322 | * @api public 2323 | */ 2324 | 2325 | function List(runner) { 2326 | Base.call(this, runner); 2327 | 2328 | var self = this 2329 | , stats = this.stats 2330 | , total = runner.total; 2331 | 2332 | runner.on('start', function(){ 2333 | console.log(JSON.stringify(['start', { total: total }])); 2334 | }); 2335 | 2336 | runner.on('pass', function(test){ 2337 | console.log(JSON.stringify(['pass', clean(test)])); 2338 | }); 2339 | 2340 | runner.on('fail', function(test, err){ 2341 | console.log(JSON.stringify(['fail', clean(test)])); 2342 | }); 2343 | 2344 | runner.on('end', function(){ 2345 | process.stdout.write(JSON.stringify(['end', self.stats])); 2346 | }); 2347 | } 2348 | 2349 | /** 2350 | * Return a plain-object representation of `test` 2351 | * free of cyclic properties etc. 2352 | * 2353 | * @param {Object} test 2354 | * @return {Object} 2355 | * @api private 2356 | */ 2357 | 2358 | function clean(test) { 2359 | return { 2360 | title: test.title 2361 | , fullTitle: test.fullTitle() 2362 | , duration: test.duration 2363 | } 2364 | } 2365 | }); // module: reporters/json-stream.js 2366 | 2367 | require.register("reporters/json.js", function(module, exports, require){ 2368 | 2369 | /** 2370 | * Module dependencies. 2371 | */ 2372 | 2373 | var Base = require('./base') 2374 | , cursor = Base.cursor 2375 | , color = Base.color; 2376 | 2377 | /** 2378 | * Expose `JSON`. 2379 | */ 2380 | 2381 | exports = module.exports = JSONReporter; 2382 | 2383 | /** 2384 | * Initialize a new `JSON` reporter. 2385 | * 2386 | * @param {Runner} runner 2387 | * @api public 2388 | */ 2389 | 2390 | function JSONReporter(runner) { 2391 | var self = this; 2392 | Base.call(this, runner); 2393 | 2394 | var tests = [] 2395 | , failures = [] 2396 | , passes = []; 2397 | 2398 | runner.on('test end', function(test){ 2399 | tests.push(test); 2400 | }); 2401 | 2402 | runner.on('pass', function(test){ 2403 | passes.push(test); 2404 | }); 2405 | 2406 | runner.on('fail', function(test){ 2407 | failures.push(test); 2408 | }); 2409 | 2410 | runner.on('end', function(){ 2411 | var obj = { 2412 | stats: self.stats 2413 | , tests: tests.map(clean) 2414 | , failures: failures.map(clean) 2415 | , passes: passes.map(clean) 2416 | }; 2417 | 2418 | process.stdout.write(JSON.stringify(obj, null, 2)); 2419 | }); 2420 | } 2421 | 2422 | /** 2423 | * Return a plain-object representation of `test` 2424 | * free of cyclic properties etc. 2425 | * 2426 | * @param {Object} test 2427 | * @return {Object} 2428 | * @api private 2429 | */ 2430 | 2431 | function clean(test) { 2432 | return { 2433 | title: test.title 2434 | , fullTitle: test.fullTitle() 2435 | , duration: test.duration 2436 | } 2437 | } 2438 | }); // module: reporters/json.js 2439 | 2440 | require.register("reporters/landing.js", function(module, exports, require){ 2441 | 2442 | /** 2443 | * Module dependencies. 2444 | */ 2445 | 2446 | var Base = require('./base') 2447 | , cursor = Base.cursor 2448 | , color = Base.color; 2449 | 2450 | /** 2451 | * Expose `Landing`. 2452 | */ 2453 | 2454 | exports = module.exports = Landing; 2455 | 2456 | /** 2457 | * Airplane color. 2458 | */ 2459 | 2460 | Base.colors.plane = 0; 2461 | 2462 | /** 2463 | * Airplane crash color. 2464 | */ 2465 | 2466 | Base.colors['plane crash'] = 31; 2467 | 2468 | /** 2469 | * Runway color. 2470 | */ 2471 | 2472 | Base.colors.runway = 90; 2473 | 2474 | /** 2475 | * Initialize a new `Landing` reporter. 2476 | * 2477 | * @param {Runner} runner 2478 | * @api public 2479 | */ 2480 | 2481 | function Landing(runner) { 2482 | Base.call(this, runner); 2483 | 2484 | var self = this 2485 | , stats = this.stats 2486 | , width = Base.window.width * .75 | 0 2487 | , total = runner.total 2488 | , stream = process.stdout 2489 | , plane = color('plane', '✈') 2490 | , crashed = -1 2491 | , n = 0; 2492 | 2493 | function runway() { 2494 | var buf = Array(width).join('-'); 2495 | return ' ' + color('runway', buf); 2496 | } 2497 | 2498 | runner.on('start', function(){ 2499 | stream.write('\n '); 2500 | cursor.hide(); 2501 | }); 2502 | 2503 | runner.on('test end', function(test){ 2504 | // check if the plane crashed 2505 | var col = -1 == crashed 2506 | ? width * ++n / total | 0 2507 | : crashed; 2508 | 2509 | // show the crash 2510 | if ('failed' == test.state) { 2511 | plane = color('plane crash', '✈'); 2512 | crashed = col; 2513 | } 2514 | 2515 | // render landing strip 2516 | stream.write('\u001b[4F\n\n'); 2517 | stream.write(runway()); 2518 | stream.write('\n '); 2519 | stream.write(color('runway', Array(col).join('⋅'))); 2520 | stream.write(plane) 2521 | stream.write(color('runway', Array(width - col).join('⋅') + '\n')); 2522 | stream.write(runway()); 2523 | stream.write('\u001b[0m'); 2524 | }); 2525 | 2526 | runner.on('end', function(){ 2527 | cursor.show(); 2528 | console.log(); 2529 | self.epilogue(); 2530 | }); 2531 | } 2532 | 2533 | /** 2534 | * Inherit from `Base.prototype`. 2535 | */ 2536 | 2537 | Landing.prototype = new Base; 2538 | Landing.prototype.constructor = Landing; 2539 | 2540 | }); // module: reporters/landing.js 2541 | 2542 | require.register("reporters/list.js", function(module, exports, require){ 2543 | 2544 | /** 2545 | * Module dependencies. 2546 | */ 2547 | 2548 | var Base = require('./base') 2549 | , cursor = Base.cursor 2550 | , color = Base.color; 2551 | 2552 | /** 2553 | * Expose `List`. 2554 | */ 2555 | 2556 | exports = module.exports = List; 2557 | 2558 | /** 2559 | * Initialize a new `List` test reporter. 2560 | * 2561 | * @param {Runner} runner 2562 | * @api public 2563 | */ 2564 | 2565 | function List(runner) { 2566 | Base.call(this, runner); 2567 | 2568 | var self = this 2569 | , stats = this.stats 2570 | , n = 0; 2571 | 2572 | runner.on('start', function(){ 2573 | console.log(); 2574 | }); 2575 | 2576 | runner.on('test', function(test){ 2577 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); 2578 | }); 2579 | 2580 | runner.on('pending', function(test){ 2581 | var fmt = color('checkmark', ' -') 2582 | + color('pending', ' %s'); 2583 | console.log(fmt, test.fullTitle()); 2584 | }); 2585 | 2586 | runner.on('pass', function(test){ 2587 | var fmt = color('checkmark', ' ✓') 2588 | + color('pass', ' %s: ') 2589 | + color(test.speed, '%dms'); 2590 | cursor.CR(); 2591 | console.log(fmt, test.fullTitle(), test.duration); 2592 | }); 2593 | 2594 | runner.on('fail', function(test, err){ 2595 | cursor.CR(); 2596 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); 2597 | }); 2598 | 2599 | runner.on('end', self.epilogue.bind(self)); 2600 | } 2601 | 2602 | /** 2603 | * Inherit from `Base.prototype`. 2604 | */ 2605 | 2606 | List.prototype = new Base; 2607 | List.prototype.constructor = List; 2608 | 2609 | 2610 | }); // module: reporters/list.js 2611 | 2612 | require.register("reporters/markdown.js", function(module, exports, require){ 2613 | /** 2614 | * Module dependencies. 2615 | */ 2616 | 2617 | var Base = require('./base') 2618 | , utils = require('../utils'); 2619 | 2620 | /** 2621 | * Expose `Markdown`. 2622 | */ 2623 | 2624 | exports = module.exports = Markdown; 2625 | 2626 | /** 2627 | * Initialize a new `Markdown` reporter. 2628 | * 2629 | * @param {Runner} runner 2630 | * @api public 2631 | */ 2632 | 2633 | function Markdown(runner) { 2634 | Base.call(this, runner); 2635 | 2636 | var self = this 2637 | , stats = this.stats 2638 | , total = runner.total 2639 | , level = 0 2640 | , buf = ''; 2641 | 2642 | function title(str) { 2643 | return Array(level).join('#') + ' ' + str; 2644 | } 2645 | 2646 | function indent() { 2647 | return Array(level).join(' '); 2648 | } 2649 | 2650 | function mapTOC(suite, obj) { 2651 | var ret = obj; 2652 | obj = obj[suite.title] = obj[suite.title] || { suite: suite }; 2653 | suite.suites.forEach(function(suite){ 2654 | mapTOC(suite, obj); 2655 | }); 2656 | return ret; 2657 | } 2658 | 2659 | function stringifyTOC(obj, level) { 2660 | ++level; 2661 | var buf = ''; 2662 | var link; 2663 | for (var key in obj) { 2664 | if ('suite' == key) continue; 2665 | if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; 2666 | if (key) buf += Array(level).join(' ') + link; 2667 | buf += stringifyTOC(obj[key], level); 2668 | } 2669 | --level; 2670 | return buf; 2671 | } 2672 | 2673 | function generateTOC(suite) { 2674 | var obj = mapTOC(suite, {}); 2675 | return stringifyTOC(obj, 0); 2676 | } 2677 | 2678 | generateTOC(runner.suite); 2679 | 2680 | runner.on('suite', function(suite){ 2681 | ++level; 2682 | var slug = utils.slug(suite.fullTitle()); 2683 | buf += '' + '\n'; 2684 | buf += title(suite.title) + '\n'; 2685 | }); 2686 | 2687 | runner.on('suite end', function(suite){ 2688 | --level; 2689 | }); 2690 | 2691 | runner.on('pass', function(test){ 2692 | var code = utils.clean(test.fn.toString()); 2693 | buf += test.title + '.\n'; 2694 | buf += '\n```js\n'; 2695 | buf += code + '\n'; 2696 | buf += '```\n\n'; 2697 | }); 2698 | 2699 | runner.on('end', function(){ 2700 | process.stdout.write('# TOC\n'); 2701 | process.stdout.write(generateTOC(runner.suite)); 2702 | process.stdout.write(buf); 2703 | }); 2704 | } 2705 | }); // module: reporters/markdown.js 2706 | 2707 | require.register("reporters/min.js", function(module, exports, require){ 2708 | 2709 | /** 2710 | * Module dependencies. 2711 | */ 2712 | 2713 | var Base = require('./base'); 2714 | 2715 | /** 2716 | * Expose `Min`. 2717 | */ 2718 | 2719 | exports = module.exports = Min; 2720 | 2721 | /** 2722 | * Initialize a new `Min` minimal test reporter (best used with --watch). 2723 | * 2724 | * @param {Runner} runner 2725 | * @api public 2726 | */ 2727 | 2728 | function Min(runner) { 2729 | Base.call(this, runner); 2730 | 2731 | runner.on('start', function(){ 2732 | // clear screen 2733 | process.stdout.write('\u001b[2J'); 2734 | // set cursor position 2735 | process.stdout.write('\u001b[1;3H'); 2736 | }); 2737 | 2738 | runner.on('end', this.epilogue.bind(this)); 2739 | } 2740 | 2741 | /** 2742 | * Inherit from `Base.prototype`. 2743 | */ 2744 | 2745 | Min.prototype = new Base; 2746 | Min.prototype.constructor = Min; 2747 | 2748 | }); // module: reporters/min.js 2749 | 2750 | require.register("reporters/nyan.js", function(module, exports, require){ 2751 | 2752 | /** 2753 | * Module dependencies. 2754 | */ 2755 | 2756 | var Base = require('./base') 2757 | , color = Base.color; 2758 | 2759 | /** 2760 | * Expose `Dot`. 2761 | */ 2762 | 2763 | exports = module.exports = NyanCat; 2764 | 2765 | /** 2766 | * Initialize a new `Dot` matrix test reporter. 2767 | * 2768 | * @param {Runner} runner 2769 | * @api public 2770 | */ 2771 | 2772 | function NyanCat(runner) { 2773 | Base.call(this, runner); 2774 | 2775 | var self = this 2776 | , stats = this.stats 2777 | , width = Base.window.width * .75 | 0 2778 | , rainbowColors = this.rainbowColors = self.generateColors() 2779 | , colorIndex = this.colorIndex = 0 2780 | , numerOfLines = this.numberOfLines = 4 2781 | , trajectories = this.trajectories = [[], [], [], []] 2782 | , nyanCatWidth = this.nyanCatWidth = 11 2783 | , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) 2784 | , scoreboardWidth = this.scoreboardWidth = 5 2785 | , tick = this.tick = 0 2786 | , n = 0; 2787 | 2788 | runner.on('start', function(){ 2789 | Base.cursor.hide(); 2790 | self.draw('start'); 2791 | }); 2792 | 2793 | runner.on('pending', function(test){ 2794 | self.draw('pending'); 2795 | }); 2796 | 2797 | runner.on('pass', function(test){ 2798 | self.draw('pass'); 2799 | }); 2800 | 2801 | runner.on('fail', function(test, err){ 2802 | self.draw('fail'); 2803 | }); 2804 | 2805 | runner.on('end', function(){ 2806 | Base.cursor.show(); 2807 | for (var i = 0; i < self.numberOfLines; i++) write('\n'); 2808 | self.epilogue(); 2809 | }); 2810 | } 2811 | 2812 | /** 2813 | * Draw the nyan cat with runner `status`. 2814 | * 2815 | * @param {String} status 2816 | * @api private 2817 | */ 2818 | 2819 | NyanCat.prototype.draw = function(status){ 2820 | this.appendRainbow(); 2821 | this.drawScoreboard(); 2822 | this.drawRainbow(); 2823 | this.drawNyanCat(status); 2824 | this.tick = !this.tick; 2825 | }; 2826 | 2827 | /** 2828 | * Draw the "scoreboard" showing the number 2829 | * of passes, failures and pending tests. 2830 | * 2831 | * @api private 2832 | */ 2833 | 2834 | NyanCat.prototype.drawScoreboard = function(){ 2835 | var stats = this.stats; 2836 | var colors = Base.colors; 2837 | 2838 | function draw(color, n) { 2839 | write(' '); 2840 | write('\u001b[' + color + 'm' + n + '\u001b[0m'); 2841 | write('\n'); 2842 | } 2843 | 2844 | draw(colors.green, stats.passes); 2845 | draw(colors.fail, stats.failures); 2846 | draw(colors.pending, stats.pending); 2847 | write('\n'); 2848 | 2849 | this.cursorUp(this.numberOfLines); 2850 | }; 2851 | 2852 | /** 2853 | * Append the rainbow. 2854 | * 2855 | * @api private 2856 | */ 2857 | 2858 | NyanCat.prototype.appendRainbow = function(){ 2859 | var segment = this.tick ? '_' : '-'; 2860 | var rainbowified = this.rainbowify(segment); 2861 | 2862 | for (var index = 0; index < this.numberOfLines; index++) { 2863 | var trajectory = this.trajectories[index]; 2864 | if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); 2865 | trajectory.push(rainbowified); 2866 | } 2867 | }; 2868 | 2869 | /** 2870 | * Draw the rainbow. 2871 | * 2872 | * @api private 2873 | */ 2874 | 2875 | NyanCat.prototype.drawRainbow = function(){ 2876 | var self = this; 2877 | 2878 | this.trajectories.forEach(function(line, index) { 2879 | write('\u001b[' + self.scoreboardWidth + 'C'); 2880 | write(line.join('')); 2881 | write('\n'); 2882 | }); 2883 | 2884 | this.cursorUp(this.numberOfLines); 2885 | }; 2886 | 2887 | /** 2888 | * Draw the nyan cat with `status`. 2889 | * 2890 | * @param {String} status 2891 | * @api private 2892 | */ 2893 | 2894 | NyanCat.prototype.drawNyanCat = function(status) { 2895 | var self = this; 2896 | var startWidth = this.scoreboardWidth + this.trajectories[0].length; 2897 | 2898 | [0, 1, 2, 3].forEach(function(index) { 2899 | write('\u001b[' + startWidth + 'C'); 2900 | 2901 | switch (index) { 2902 | case 0: 2903 | write('_,------,'); 2904 | write('\n'); 2905 | break; 2906 | case 1: 2907 | var padding = self.tick ? ' ' : ' '; 2908 | write('_|' + padding + '/\\_/\\ '); 2909 | write('\n'); 2910 | break; 2911 | case 2: 2912 | var padding = self.tick ? '_' : '__'; 2913 | var tail = self.tick ? '~' : '^'; 2914 | var face; 2915 | switch (status) { 2916 | case 'pass': 2917 | face = '( ^ .^)'; 2918 | break; 2919 | case 'fail': 2920 | face = '( o .o)'; 2921 | break; 2922 | default: 2923 | face = '( - .-)'; 2924 | } 2925 | write(tail + '|' + padding + face + ' '); 2926 | write('\n'); 2927 | break; 2928 | case 3: 2929 | var padding = self.tick ? ' ' : ' '; 2930 | write(padding + '"" "" '); 2931 | write('\n'); 2932 | break; 2933 | } 2934 | }); 2935 | 2936 | this.cursorUp(this.numberOfLines); 2937 | }; 2938 | 2939 | /** 2940 | * Move cursor up `n`. 2941 | * 2942 | * @param {Number} n 2943 | * @api private 2944 | */ 2945 | 2946 | NyanCat.prototype.cursorUp = function(n) { 2947 | write('\u001b[' + n + 'A'); 2948 | }; 2949 | 2950 | /** 2951 | * Move cursor down `n`. 2952 | * 2953 | * @param {Number} n 2954 | * @api private 2955 | */ 2956 | 2957 | NyanCat.prototype.cursorDown = function(n) { 2958 | write('\u001b[' + n + 'B'); 2959 | }; 2960 | 2961 | /** 2962 | * Generate rainbow colors. 2963 | * 2964 | * @return {Array} 2965 | * @api private 2966 | */ 2967 | 2968 | NyanCat.prototype.generateColors = function(){ 2969 | var colors = []; 2970 | 2971 | for (var i = 0; i < (6 * 7); i++) { 2972 | var pi3 = Math.floor(Math.PI / 3); 2973 | var n = (i * (1.0 / 6)); 2974 | var r = Math.floor(3 * Math.sin(n) + 3); 2975 | var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); 2976 | var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); 2977 | colors.push(36 * r + 6 * g + b + 16); 2978 | } 2979 | 2980 | return colors; 2981 | }; 2982 | 2983 | /** 2984 | * Apply rainbow to the given `str`. 2985 | * 2986 | * @param {String} str 2987 | * @return {String} 2988 | * @api private 2989 | */ 2990 | 2991 | NyanCat.prototype.rainbowify = function(str){ 2992 | var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; 2993 | this.colorIndex += 1; 2994 | return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; 2995 | }; 2996 | 2997 | /** 2998 | * Stdout helper. 2999 | */ 3000 | 3001 | function write(string) { 3002 | process.stdout.write(string); 3003 | } 3004 | 3005 | /** 3006 | * Inherit from `Base.prototype`. 3007 | */ 3008 | 3009 | NyanCat.prototype = new Base; 3010 | NyanCat.prototype.constructor = NyanCat; 3011 | 3012 | 3013 | }); // module: reporters/nyan.js 3014 | 3015 | require.register("reporters/progress.js", function(module, exports, require){ 3016 | 3017 | /** 3018 | * Module dependencies. 3019 | */ 3020 | 3021 | var Base = require('./base') 3022 | , cursor = Base.cursor 3023 | , color = Base.color; 3024 | 3025 | /** 3026 | * Expose `Progress`. 3027 | */ 3028 | 3029 | exports = module.exports = Progress; 3030 | 3031 | /** 3032 | * General progress bar color. 3033 | */ 3034 | 3035 | Base.colors.progress = 90; 3036 | 3037 | /** 3038 | * Initialize a new `Progress` bar test reporter. 3039 | * 3040 | * @param {Runner} runner 3041 | * @param {Object} options 3042 | * @api public 3043 | */ 3044 | 3045 | function Progress(runner, options) { 3046 | Base.call(this, runner); 3047 | 3048 | var self = this 3049 | , options = options || {} 3050 | , stats = this.stats 3051 | , width = Base.window.width * .50 | 0 3052 | , total = runner.total 3053 | , complete = 0 3054 | , max = Math.max; 3055 | 3056 | // default chars 3057 | options.open = options.open || '['; 3058 | options.complete = options.complete || '▬'; 3059 | options.incomplete = options.incomplete || '⋅'; 3060 | options.close = options.close || ']'; 3061 | options.verbose = false; 3062 | 3063 | // tests started 3064 | runner.on('start', function(){ 3065 | console.log(); 3066 | cursor.hide(); 3067 | }); 3068 | 3069 | // tests complete 3070 | runner.on('test end', function(){ 3071 | complete++; 3072 | var incomplete = total - complete 3073 | , percent = complete / total 3074 | , n = width * percent | 0 3075 | , i = width - n; 3076 | 3077 | cursor.CR(); 3078 | process.stdout.write('\u001b[J'); 3079 | process.stdout.write(color('progress', ' ' + options.open)); 3080 | process.stdout.write(Array(n).join(options.complete)); 3081 | process.stdout.write(Array(i).join(options.incomplete)); 3082 | process.stdout.write(color('progress', options.close)); 3083 | if (options.verbose) { 3084 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); 3085 | } 3086 | }); 3087 | 3088 | // tests are complete, output some stats 3089 | // and the failures if any 3090 | runner.on('end', function(){ 3091 | cursor.show(); 3092 | console.log(); 3093 | self.epilogue(); 3094 | }); 3095 | } 3096 | 3097 | /** 3098 | * Inherit from `Base.prototype`. 3099 | */ 3100 | 3101 | Progress.prototype = new Base; 3102 | Progress.prototype.constructor = Progress; 3103 | 3104 | 3105 | }); // module: reporters/progress.js 3106 | 3107 | require.register("reporters/spec.js", function(module, exports, require){ 3108 | 3109 | /** 3110 | * Module dependencies. 3111 | */ 3112 | 3113 | var Base = require('./base') 3114 | , cursor = Base.cursor 3115 | , color = Base.color; 3116 | 3117 | /** 3118 | * Expose `Spec`. 3119 | */ 3120 | 3121 | exports = module.exports = Spec; 3122 | 3123 | /** 3124 | * Initialize a new `Spec` test reporter. 3125 | * 3126 | * @param {Runner} runner 3127 | * @api public 3128 | */ 3129 | 3130 | function Spec(runner) { 3131 | Base.call(this, runner); 3132 | 3133 | var self = this 3134 | , stats = this.stats 3135 | , indents = 0 3136 | , n = 0; 3137 | 3138 | function indent() { 3139 | return Array(indents).join(' ') 3140 | } 3141 | 3142 | runner.on('start', function(){ 3143 | console.log(); 3144 | }); 3145 | 3146 | runner.on('suite', function(suite){ 3147 | ++indents; 3148 | console.log(color('suite', '%s%s'), indent(), suite.title); 3149 | }); 3150 | 3151 | runner.on('suite end', function(suite){ 3152 | --indents; 3153 | if (1 == indents) console.log(); 3154 | }); 3155 | 3156 | runner.on('test', function(test){ 3157 | process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); 3158 | }); 3159 | 3160 | runner.on('pending', function(test){ 3161 | var fmt = indent() + color('pending', ' - %s'); 3162 | console.log(fmt, test.title); 3163 | }); 3164 | 3165 | runner.on('pass', function(test){ 3166 | if ('fast' == test.speed) { 3167 | var fmt = indent() 3168 | + color('checkmark', ' ✓') 3169 | + color('pass', ' %s '); 3170 | cursor.CR(); 3171 | console.log(fmt, test.title); 3172 | } else { 3173 | var fmt = indent() 3174 | + color('checkmark', ' ✓') 3175 | + color('pass', ' %s ') 3176 | + color(test.speed, '(%dms)'); 3177 | cursor.CR(); 3178 | console.log(fmt, test.title, test.duration); 3179 | } 3180 | }); 3181 | 3182 | runner.on('fail', function(test, err){ 3183 | cursor.CR(); 3184 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); 3185 | }); 3186 | 3187 | runner.on('end', self.epilogue.bind(self)); 3188 | } 3189 | 3190 | /** 3191 | * Inherit from `Base.prototype`. 3192 | */ 3193 | 3194 | Spec.prototype = new Base; 3195 | Spec.prototype.constructor = Spec; 3196 | 3197 | 3198 | }); // module: reporters/spec.js 3199 | 3200 | require.register("reporters/tap.js", function(module, exports, require){ 3201 | 3202 | /** 3203 | * Module dependencies. 3204 | */ 3205 | 3206 | var Base = require('./base') 3207 | , cursor = Base.cursor 3208 | , color = Base.color; 3209 | 3210 | /** 3211 | * Expose `TAP`. 3212 | */ 3213 | 3214 | exports = module.exports = TAP; 3215 | 3216 | /** 3217 | * Initialize a new `TAP` reporter. 3218 | * 3219 | * @param {Runner} runner 3220 | * @api public 3221 | */ 3222 | 3223 | function TAP(runner) { 3224 | Base.call(this, runner); 3225 | 3226 | var self = this 3227 | , stats = this.stats 3228 | , n = 1; 3229 | 3230 | runner.on('start', function(){ 3231 | var total = runner.grepTotal(runner.suite); 3232 | console.log('%d..%d', 1, total); 3233 | }); 3234 | 3235 | runner.on('test end', function(){ 3236 | ++n; 3237 | }); 3238 | 3239 | runner.on('pending', function(test){ 3240 | console.log('ok %d %s # SKIP -', n, title(test)); 3241 | }); 3242 | 3243 | runner.on('pass', function(test){ 3244 | console.log('ok %d %s', n, title(test)); 3245 | }); 3246 | 3247 | runner.on('fail', function(test, err){ 3248 | console.log('not ok %d %s', n, title(test)); 3249 | console.log(err.stack.replace(/^/gm, ' ')); 3250 | }); 3251 | } 3252 | 3253 | /** 3254 | * Return a TAP-safe title of `test` 3255 | * 3256 | * @param {Object} test 3257 | * @return {String} 3258 | * @api private 3259 | */ 3260 | 3261 | function title(test) { 3262 | return test.fullTitle().replace(/#/g, ''); 3263 | } 3264 | 3265 | }); // module: reporters/tap.js 3266 | 3267 | require.register("reporters/teamcity.js", function(module, exports, require){ 3268 | 3269 | /** 3270 | * Module dependencies. 3271 | */ 3272 | 3273 | var Base = require('./base'); 3274 | 3275 | /** 3276 | * Expose `Teamcity`. 3277 | */ 3278 | 3279 | exports = module.exports = Teamcity; 3280 | 3281 | /** 3282 | * Initialize a new `Teamcity` reporter. 3283 | * 3284 | * @param {Runner} runner 3285 | * @api public 3286 | */ 3287 | 3288 | function Teamcity(runner) { 3289 | Base.call(this, runner); 3290 | var stats = this.stats; 3291 | 3292 | runner.on('start', function() { 3293 | console.log("##teamcity[testSuiteStarted name='mocha.suite']"); 3294 | }); 3295 | 3296 | runner.on('test', function(test) { 3297 | console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); 3298 | }); 3299 | 3300 | runner.on('fail', function(test, err) { 3301 | console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); 3302 | }); 3303 | 3304 | runner.on('pending', function(test) { 3305 | console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); 3306 | }); 3307 | 3308 | runner.on('test end', function(test) { 3309 | console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); 3310 | }); 3311 | 3312 | runner.on('end', function() { 3313 | console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); 3314 | }); 3315 | } 3316 | 3317 | /** 3318 | * Escape the given `str`. 3319 | */ 3320 | 3321 | function escape(str) { 3322 | return str 3323 | .replace(/\|/g, "||") 3324 | .replace(/\n/g, "|n") 3325 | .replace(/\r/g, "|r") 3326 | .replace(/\[/g, "|[") 3327 | .replace(/\]/g, "|]") 3328 | .replace(/\u0085/g, "|x") 3329 | .replace(/\u2028/g, "|l") 3330 | .replace(/\u2029/g, "|p") 3331 | .replace(/'/g, "|'"); 3332 | } 3333 | 3334 | }); // module: reporters/teamcity.js 3335 | 3336 | require.register("reporters/xunit.js", function(module, exports, require){ 3337 | 3338 | /** 3339 | * Module dependencies. 3340 | */ 3341 | 3342 | var Base = require('./base') 3343 | , utils = require('../utils') 3344 | , escape = utils.escape; 3345 | 3346 | /** 3347 | * Save timer references to avoid Sinon interfering (see GH-237). 3348 | */ 3349 | 3350 | var Date = global.Date 3351 | , setTimeout = global.setTimeout 3352 | , setInterval = global.setInterval 3353 | , clearTimeout = global.clearTimeout 3354 | , clearInterval = global.clearInterval; 3355 | 3356 | /** 3357 | * Expose `XUnit`. 3358 | */ 3359 | 3360 | exports = module.exports = XUnit; 3361 | 3362 | /** 3363 | * Initialize a new `XUnit` reporter. 3364 | * 3365 | * @param {Runner} runner 3366 | * @api public 3367 | */ 3368 | 3369 | function XUnit(runner) { 3370 | Base.call(this, runner); 3371 | var stats = this.stats 3372 | , tests = [] 3373 | , self = this; 3374 | 3375 | runner.on('pass', function(test){ 3376 | tests.push(test); 3377 | }); 3378 | 3379 | runner.on('fail', function(test){ 3380 | tests.push(test); 3381 | }); 3382 | 3383 | runner.on('end', function(){ 3384 | console.log(tag('testsuite', { 3385 | name: 'Mocha Tests' 3386 | , tests: stats.tests 3387 | , failures: stats.failures 3388 | , errors: stats.failures 3389 | , skip: stats.tests - stats.failures - stats.passes 3390 | , timestamp: (new Date).toUTCString() 3391 | , time: stats.duration / 1000 3392 | }, false)); 3393 | 3394 | tests.forEach(test); 3395 | console.log(''); 3396 | }); 3397 | } 3398 | 3399 | /** 3400 | * Inherit from `Base.prototype`. 3401 | */ 3402 | 3403 | XUnit.prototype = new Base; 3404 | XUnit.prototype.constructor = XUnit; 3405 | 3406 | 3407 | /** 3408 | * Output tag for the given `test.` 3409 | */ 3410 | 3411 | function test(test) { 3412 | var attrs = { 3413 | classname: test.parent.fullTitle() 3414 | , name: test.title 3415 | , time: test.duration / 1000 3416 | }; 3417 | 3418 | if ('failed' == test.state) { 3419 | var err = test.err; 3420 | attrs.message = escape(err.message); 3421 | console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); 3422 | } else if (test.pending) { 3423 | console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); 3424 | } else { 3425 | console.log(tag('testcase', attrs, true) ); 3426 | } 3427 | } 3428 | 3429 | /** 3430 | * HTML tag helper. 3431 | */ 3432 | 3433 | function tag(name, attrs, close, content) { 3434 | var end = close ? '/>' : '>' 3435 | , pairs = [] 3436 | , tag; 3437 | 3438 | for (var key in attrs) { 3439 | pairs.push(key + '="' + escape(attrs[key]) + '"'); 3440 | } 3441 | 3442 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; 3443 | if (content) tag += content + ''; 3453 | } 3454 | 3455 | }); // module: reporters/xunit.js 3456 | 3457 | require.register("runnable.js", function(module, exports, require){ 3458 | 3459 | /** 3460 | * Module dependencies. 3461 | */ 3462 | 3463 | var EventEmitter = require('browser/events').EventEmitter 3464 | , debug = require('browser/debug')('mocha:runnable'); 3465 | 3466 | /** 3467 | * Save timer references to avoid Sinon interfering (see GH-237). 3468 | */ 3469 | 3470 | var Date = global.Date 3471 | , setTimeout = global.setTimeout 3472 | , setInterval = global.setInterval 3473 | , clearTimeout = global.clearTimeout 3474 | , clearInterval = global.clearInterval; 3475 | 3476 | /** 3477 | * Expose `Runnable`. 3478 | */ 3479 | 3480 | module.exports = Runnable; 3481 | 3482 | /** 3483 | * Initialize a new `Runnable` with the given `title` and callback `fn`. 3484 | * 3485 | * @param {String} title 3486 | * @param {Function} fn 3487 | * @api private 3488 | */ 3489 | 3490 | function Runnable(title, fn) { 3491 | this.title = title; 3492 | this.fn = fn; 3493 | this.async = fn && fn.length; 3494 | this.sync = ! this.async; 3495 | this._timeout = 2000; 3496 | this._slow = 75; 3497 | this.timedOut = false; 3498 | } 3499 | 3500 | /** 3501 | * Inherit from `EventEmitter.prototype`. 3502 | */ 3503 | 3504 | Runnable.prototype = new EventEmitter; 3505 | Runnable.prototype.constructor = Runnable; 3506 | 3507 | 3508 | /** 3509 | * Set & get timeout `ms`. 3510 | * 3511 | * @param {Number} ms 3512 | * @return {Runnable|Number} ms or self 3513 | * @api private 3514 | */ 3515 | 3516 | Runnable.prototype.timeout = function(ms){ 3517 | if (0 == arguments.length) return this._timeout; 3518 | debug('timeout %d', ms); 3519 | this._timeout = ms; 3520 | if (this.timer) this.resetTimeout(); 3521 | return this; 3522 | }; 3523 | 3524 | /** 3525 | * Set & get slow `ms`. 3526 | * 3527 | * @param {Number} ms 3528 | * @return {Runnable|Number} ms or self 3529 | * @api private 3530 | */ 3531 | 3532 | Runnable.prototype.slow = function(ms){ 3533 | if (0 === arguments.length) return this._slow; 3534 | debug('timeout %d', ms); 3535 | this._slow = ms; 3536 | return this; 3537 | }; 3538 | 3539 | /** 3540 | * Return the full title generated by recursively 3541 | * concatenating the parent's full title. 3542 | * 3543 | * @return {String} 3544 | * @api public 3545 | */ 3546 | 3547 | Runnable.prototype.fullTitle = function(){ 3548 | return this.parent.fullTitle() + ' ' + this.title; 3549 | }; 3550 | 3551 | /** 3552 | * Clear the timeout. 3553 | * 3554 | * @api private 3555 | */ 3556 | 3557 | Runnable.prototype.clearTimeout = function(){ 3558 | clearTimeout(this.timer); 3559 | }; 3560 | 3561 | /** 3562 | * Inspect the runnable void of private properties. 3563 | * 3564 | * @return {String} 3565 | * @api private 3566 | */ 3567 | 3568 | Runnable.prototype.inspect = function(){ 3569 | return JSON.stringify(this, function(key, val){ 3570 | if ('_' == key[0]) return; 3571 | if ('parent' == key) return '#'; 3572 | if ('ctx' == key) return '#'; 3573 | return val; 3574 | }, 2); 3575 | }; 3576 | 3577 | /** 3578 | * Reset the timeout. 3579 | * 3580 | * @api private 3581 | */ 3582 | 3583 | Runnable.prototype.resetTimeout = function(){ 3584 | var self = this 3585 | , ms = this.timeout(); 3586 | 3587 | this.clearTimeout(); 3588 | if (ms) { 3589 | this.timer = setTimeout(function(){ 3590 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); 3591 | self.timedOut = true; 3592 | }, ms); 3593 | } 3594 | }; 3595 | 3596 | /** 3597 | * Run the test and invoke `fn(err)`. 3598 | * 3599 | * @param {Function} fn 3600 | * @api private 3601 | */ 3602 | 3603 | Runnable.prototype.run = function(fn){ 3604 | var self = this 3605 | , ms = this.timeout() 3606 | , start = new Date 3607 | , ctx = this.ctx 3608 | , finished 3609 | , emitted; 3610 | 3611 | if (ctx) ctx.runnable(this); 3612 | 3613 | // timeout 3614 | if (this.async) { 3615 | if (ms) { 3616 | this.timer = setTimeout(function(){ 3617 | done(new Error('timeout of ' + ms + 'ms exceeded')); 3618 | self.timedOut = true; 3619 | }, ms); 3620 | } 3621 | } 3622 | 3623 | // called multiple times 3624 | function multiple(err) { 3625 | if (emitted) return; 3626 | emitted = true; 3627 | self.emit('error', err || new Error('done() called multiple times')); 3628 | } 3629 | 3630 | // finished 3631 | function done(err) { 3632 | if (self.timedOut) return; 3633 | if (finished) return multiple(err); 3634 | self.clearTimeout(); 3635 | self.duration = new Date - start; 3636 | finished = true; 3637 | fn(err); 3638 | } 3639 | 3640 | // for .resetTimeout() 3641 | this.callback = done; 3642 | 3643 | // async 3644 | if (this.async) { 3645 | try { 3646 | this.fn.call(ctx, function(err){ 3647 | if (err instanceof Error) return done(err); 3648 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); 3649 | done(); 3650 | }); 3651 | } catch (err) { 3652 | done(err); 3653 | } 3654 | return; 3655 | } 3656 | 3657 | // sync 3658 | try { 3659 | if (!this.pending) this.fn.call(ctx); 3660 | this.duration = new Date - start; 3661 | fn(); 3662 | } catch (err) { 3663 | fn(err); 3664 | } 3665 | }; 3666 | 3667 | }); // module: runnable.js 3668 | 3669 | require.register("runner.js", function(module, exports, require){ 3670 | 3671 | /** 3672 | * Module dependencies. 3673 | */ 3674 | 3675 | var EventEmitter = require('browser/events').EventEmitter 3676 | , debug = require('browser/debug')('mocha:runner') 3677 | , Test = require('./test') 3678 | , utils = require('./utils') 3679 | , filter = utils.filter 3680 | , keys = utils.keys 3681 | , noop = function(){}; 3682 | 3683 | /** 3684 | * Expose `Runner`. 3685 | */ 3686 | 3687 | module.exports = Runner; 3688 | 3689 | /** 3690 | * Initialize a `Runner` for the given `suite`. 3691 | * 3692 | * Events: 3693 | * 3694 | * - `start` execution started 3695 | * - `end` execution complete 3696 | * - `suite` (suite) test suite execution started 3697 | * - `suite end` (suite) all tests (and sub-suites) have finished 3698 | * - `test` (test) test execution started 3699 | * - `test end` (test) test completed 3700 | * - `hook` (hook) hook execution started 3701 | * - `hook end` (hook) hook complete 3702 | * - `pass` (test) test passed 3703 | * - `fail` (test, err) test failed 3704 | * 3705 | * @api public 3706 | */ 3707 | 3708 | function Runner(suite) { 3709 | var self = this; 3710 | this._globals = []; 3711 | this.suite = suite; 3712 | this.total = suite.total(); 3713 | this.failures = 0; 3714 | this.on('test end', function(test){ self.checkGlobals(test); }); 3715 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); 3716 | this.grep(/.*/); 3717 | this.globals(utils.keys(global).concat(['errno'])); 3718 | } 3719 | 3720 | /** 3721 | * Inherit from `EventEmitter.prototype`. 3722 | */ 3723 | 3724 | Runner.prototype = new EventEmitter; 3725 | Runner.prototype.constructor = Runner; 3726 | 3727 | 3728 | /** 3729 | * Run tests with full titles matching `re`. Updates runner.total 3730 | * with number of tests matched. 3731 | * 3732 | * @param {RegExp} re 3733 | * @param {Boolean} invert 3734 | * @return {Runner} for chaining 3735 | * @api public 3736 | */ 3737 | 3738 | Runner.prototype.grep = function(re, invert){ 3739 | debug('grep %s', re); 3740 | this._grep = re; 3741 | this._invert = invert; 3742 | this.total = this.grepTotal(this.suite); 3743 | return this; 3744 | }; 3745 | 3746 | /** 3747 | * Returns the number of tests matching the grep search for the 3748 | * given suite. 3749 | * 3750 | * @param {Suite} suite 3751 | * @return {Number} 3752 | * @api public 3753 | */ 3754 | 3755 | Runner.prototype.grepTotal = function(suite) { 3756 | var self = this; 3757 | var total = 0; 3758 | 3759 | suite.eachTest(function(test){ 3760 | var match = self._grep.test(test.fullTitle()); 3761 | if (self._invert) match = !match; 3762 | if (match) total++; 3763 | }); 3764 | 3765 | return total; 3766 | }; 3767 | 3768 | /** 3769 | * Allow the given `arr` of globals. 3770 | * 3771 | * @param {Array} arr 3772 | * @return {Runner} for chaining 3773 | * @api public 3774 | */ 3775 | 3776 | Runner.prototype.globals = function(arr){ 3777 | if (0 == arguments.length) return this._globals; 3778 | debug('globals %j', arr); 3779 | utils.forEach(arr, function(arr){ 3780 | this._globals.push(arr); 3781 | }, this); 3782 | return this; 3783 | }; 3784 | 3785 | /** 3786 | * Check for global variable leaks. 3787 | * 3788 | * @api private 3789 | */ 3790 | 3791 | Runner.prototype.checkGlobals = function(test){ 3792 | if (this.ignoreLeaks) return; 3793 | var ok = this._globals; 3794 | var globals = keys(global); 3795 | var isNode = process.kill; 3796 | var leaks; 3797 | 3798 | // check length - 2 ('errno' and 'location' globals) 3799 | if (isNode && 1 == ok.length - globals.length) return 3800 | else if (2 == ok.length - globals.length) return; 3801 | 3802 | leaks = filterLeaks(ok, globals); 3803 | this._globals = this._globals.concat(leaks); 3804 | 3805 | if (leaks.length > 1) { 3806 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); 3807 | } else if (leaks.length) { 3808 | this.fail(test, new Error('global leak detected: ' + leaks[0])); 3809 | } 3810 | }; 3811 | 3812 | /** 3813 | * Fail the given `test`. 3814 | * 3815 | * @param {Test} test 3816 | * @param {Error} err 3817 | * @api private 3818 | */ 3819 | 3820 | Runner.prototype.fail = function(test, err){ 3821 | ++this.failures; 3822 | test.state = 'failed'; 3823 | if ('string' == typeof err) { 3824 | err = new Error('the string "' + err + '" was thrown, throw an Error :)'); 3825 | } 3826 | this.emit('fail', test, err); 3827 | }; 3828 | 3829 | /** 3830 | * Fail the given `hook` with `err`. 3831 | * 3832 | * Hook failures (currently) hard-end due 3833 | * to that fact that a failing hook will 3834 | * surely cause subsequent tests to fail, 3835 | * causing jumbled reporting. 3836 | * 3837 | * @param {Hook} hook 3838 | * @param {Error} err 3839 | * @api private 3840 | */ 3841 | 3842 | Runner.prototype.failHook = function(hook, err){ 3843 | this.fail(hook, err); 3844 | this.emit('end'); 3845 | }; 3846 | 3847 | /** 3848 | * Run hook `name` callbacks and then invoke `fn()`. 3849 | * 3850 | * @param {String} name 3851 | * @param {Function} function 3852 | * @api private 3853 | */ 3854 | 3855 | Runner.prototype.hook = function(name, fn){ 3856 | var suite = this.suite 3857 | , hooks = suite['_' + name] 3858 | , self = this 3859 | , timer; 3860 | 3861 | function next(i) { 3862 | var hook = hooks[i]; 3863 | if (!hook) return fn(); 3864 | self.currentRunnable = hook; 3865 | 3866 | self.emit('hook', hook); 3867 | 3868 | hook.on('error', function(err){ 3869 | self.failHook(hook, err); 3870 | }); 3871 | 3872 | hook.run(function(err){ 3873 | hook.removeAllListeners('error'); 3874 | var testError = hook.error(); 3875 | if (testError) self.fail(self.test, testError); 3876 | if (err) return self.failHook(hook, err); 3877 | self.emit('hook end', hook); 3878 | next(++i); 3879 | }); 3880 | } 3881 | 3882 | process.nextTick(function(){ 3883 | next(0); 3884 | }); 3885 | }; 3886 | 3887 | /** 3888 | * Run hook `name` for the given array of `suites` 3889 | * in order, and callback `fn(err)`. 3890 | * 3891 | * @param {String} name 3892 | * @param {Array} suites 3893 | * @param {Function} fn 3894 | * @api private 3895 | */ 3896 | 3897 | Runner.prototype.hooks = function(name, suites, fn){ 3898 | var self = this 3899 | , orig = this.suite; 3900 | 3901 | function next(suite) { 3902 | self.suite = suite; 3903 | 3904 | if (!suite) { 3905 | self.suite = orig; 3906 | return fn(); 3907 | } 3908 | 3909 | self.hook(name, function(err){ 3910 | if (err) { 3911 | self.suite = orig; 3912 | return fn(err); 3913 | } 3914 | 3915 | next(suites.pop()); 3916 | }); 3917 | } 3918 | 3919 | next(suites.pop()); 3920 | }; 3921 | 3922 | /** 3923 | * Run hooks from the top level down. 3924 | * 3925 | * @param {String} name 3926 | * @param {Function} fn 3927 | * @api private 3928 | */ 3929 | 3930 | Runner.prototype.hookUp = function(name, fn){ 3931 | var suites = [this.suite].concat(this.parents()).reverse(); 3932 | this.hooks(name, suites, fn); 3933 | }; 3934 | 3935 | /** 3936 | * Run hooks from the bottom up. 3937 | * 3938 | * @param {String} name 3939 | * @param {Function} fn 3940 | * @api private 3941 | */ 3942 | 3943 | Runner.prototype.hookDown = function(name, fn){ 3944 | var suites = [this.suite].concat(this.parents()); 3945 | this.hooks(name, suites, fn); 3946 | }; 3947 | 3948 | /** 3949 | * Return an array of parent Suites from 3950 | * closest to furthest. 3951 | * 3952 | * @return {Array} 3953 | * @api private 3954 | */ 3955 | 3956 | Runner.prototype.parents = function(){ 3957 | var suite = this.suite 3958 | , suites = []; 3959 | while (suite = suite.parent) suites.push(suite); 3960 | return suites; 3961 | }; 3962 | 3963 | /** 3964 | * Run the current test and callback `fn(err)`. 3965 | * 3966 | * @param {Function} fn 3967 | * @api private 3968 | */ 3969 | 3970 | Runner.prototype.runTest = function(fn){ 3971 | var test = this.test 3972 | , self = this; 3973 | 3974 | try { 3975 | test.on('error', function(err){ 3976 | self.fail(test, err); 3977 | }); 3978 | test.run(fn); 3979 | } catch (err) { 3980 | fn(err); 3981 | } 3982 | }; 3983 | 3984 | /** 3985 | * Run tests in the given `suite` and invoke 3986 | * the callback `fn()` when complete. 3987 | * 3988 | * @param {Suite} suite 3989 | * @param {Function} fn 3990 | * @api private 3991 | */ 3992 | 3993 | Runner.prototype.runTests = function(suite, fn){ 3994 | var self = this 3995 | , tests = suite.tests 3996 | , test; 3997 | 3998 | function next(err) { 3999 | // if we bail after first err 4000 | if (self.failures && suite._bail) return fn(); 4001 | 4002 | // next test 4003 | test = tests.shift(); 4004 | 4005 | // all done 4006 | if (!test) return fn(); 4007 | 4008 | // grep 4009 | var match = self._grep.test(test.fullTitle()); 4010 | if (self._invert) match = !match; 4011 | if (!match) return next(); 4012 | 4013 | // pending 4014 | if (test.pending) { 4015 | self.emit('pending', test); 4016 | self.emit('test end', test); 4017 | return next(); 4018 | } 4019 | 4020 | // execute test and hook(s) 4021 | self.emit('test', self.test = test); 4022 | self.hookDown('beforeEach', function(){ 4023 | self.currentRunnable = self.test; 4024 | self.runTest(function(err){ 4025 | test = self.test; 4026 | 4027 | if (err) { 4028 | self.fail(test, err); 4029 | self.emit('test end', test); 4030 | return self.hookUp('afterEach', next); 4031 | } 4032 | 4033 | test.state = 'passed'; 4034 | self.emit('pass', test); 4035 | self.emit('test end', test); 4036 | self.hookUp('afterEach', next); 4037 | }); 4038 | }); 4039 | } 4040 | 4041 | this.next = next; 4042 | next(); 4043 | }; 4044 | 4045 | /** 4046 | * Run the given `suite` and invoke the 4047 | * callback `fn()` when complete. 4048 | * 4049 | * @param {Suite} suite 4050 | * @param {Function} fn 4051 | * @api private 4052 | */ 4053 | 4054 | Runner.prototype.runSuite = function(suite, fn){ 4055 | var total = this.grepTotal(suite) 4056 | , self = this 4057 | , i = 0; 4058 | 4059 | debug('run suite %s', suite.fullTitle()); 4060 | 4061 | if (!total) return fn(); 4062 | 4063 | this.emit('suite', this.suite = suite); 4064 | 4065 | function next() { 4066 | var curr = suite.suites[i++]; 4067 | if (!curr) return done(); 4068 | self.runSuite(curr, next); 4069 | } 4070 | 4071 | function done() { 4072 | self.suite = suite; 4073 | self.hook('afterAll', function(){ 4074 | self.emit('suite end', suite); 4075 | fn(); 4076 | }); 4077 | } 4078 | 4079 | this.hook('beforeAll', function(){ 4080 | self.runTests(suite, next); 4081 | }); 4082 | }; 4083 | 4084 | /** 4085 | * Handle uncaught exceptions. 4086 | * 4087 | * @param {Error} err 4088 | * @api private 4089 | */ 4090 | 4091 | Runner.prototype.uncaught = function(err){ 4092 | debug('uncaught exception %s', err.message); 4093 | var runnable = this.currentRunnable; 4094 | if (!runnable || 'failed' == runnable.state) return; 4095 | runnable.clearTimeout(); 4096 | err.uncaught = true; 4097 | this.fail(runnable, err); 4098 | 4099 | // recover from test 4100 | if ('test' == runnable.type) { 4101 | this.emit('test end', runnable); 4102 | this.hookUp('afterEach', this.next); 4103 | return; 4104 | } 4105 | 4106 | // bail on hooks 4107 | this.emit('end'); 4108 | }; 4109 | 4110 | /** 4111 | * Run the root suite and invoke `fn(failures)` 4112 | * on completion. 4113 | * 4114 | * @param {Function} fn 4115 | * @return {Runner} for chaining 4116 | * @api public 4117 | */ 4118 | 4119 | Runner.prototype.run = function(fn){ 4120 | var self = this 4121 | , fn = fn || function(){}; 4122 | 4123 | debug('start'); 4124 | 4125 | // uncaught callback 4126 | function uncaught(err) { 4127 | self.uncaught(err); 4128 | } 4129 | 4130 | // callback 4131 | this.on('end', function(){ 4132 | debug('end'); 4133 | process.removeListener('uncaughtException', uncaught); 4134 | fn(self.failures); 4135 | }); 4136 | 4137 | // run suites 4138 | this.emit('start'); 4139 | this.runSuite(this.suite, function(){ 4140 | debug('finished running'); 4141 | self.emit('end'); 4142 | }); 4143 | 4144 | // uncaught exception 4145 | process.on('uncaughtException', uncaught); 4146 | 4147 | return this; 4148 | }; 4149 | 4150 | /** 4151 | * Filter leaks with the given globals flagged as `ok`. 4152 | * 4153 | * @param {Array} ok 4154 | * @param {Array} globals 4155 | * @return {Array} 4156 | * @api private 4157 | */ 4158 | 4159 | function filterLeaks(ok, globals) { 4160 | return filter(globals, function(key){ 4161 | var matched = filter(ok, function(ok){ 4162 | if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); 4163 | return key == ok; 4164 | }); 4165 | return matched.length == 0 && (!global.navigator || 'onerror' !== key); 4166 | }); 4167 | } 4168 | 4169 | }); // module: runner.js 4170 | 4171 | require.register("suite.js", function(module, exports, require){ 4172 | 4173 | /** 4174 | * Module dependencies. 4175 | */ 4176 | 4177 | var EventEmitter = require('browser/events').EventEmitter 4178 | , debug = require('browser/debug')('mocha:suite') 4179 | , milliseconds = require('./ms') 4180 | , utils = require('./utils') 4181 | , Hook = require('./hook'); 4182 | 4183 | /** 4184 | * Expose `Suite`. 4185 | */ 4186 | 4187 | exports = module.exports = Suite; 4188 | 4189 | /** 4190 | * Create a new `Suite` with the given `title` 4191 | * and parent `Suite`. When a suite with the 4192 | * same title is already present, that suite 4193 | * is returned to provide nicer reporter 4194 | * and more flexible meta-testing. 4195 | * 4196 | * @param {Suite} parent 4197 | * @param {String} title 4198 | * @return {Suite} 4199 | * @api public 4200 | */ 4201 | 4202 | exports.create = function(parent, title){ 4203 | var suite = new Suite(title, parent.ctx); 4204 | suite.parent = parent; 4205 | if (parent.pending) suite.pending = true; 4206 | title = suite.fullTitle(); 4207 | parent.addSuite(suite); 4208 | return suite; 4209 | }; 4210 | 4211 | /** 4212 | * Initialize a new `Suite` with the given 4213 | * `title` and `ctx`. 4214 | * 4215 | * @param {String} title 4216 | * @param {Context} ctx 4217 | * @api private 4218 | */ 4219 | 4220 | function Suite(title, ctx) { 4221 | this.title = title; 4222 | this.ctx = ctx; 4223 | this.suites = []; 4224 | this.tests = []; 4225 | this.pending = false; 4226 | this._beforeEach = []; 4227 | this._beforeAll = []; 4228 | this._afterEach = []; 4229 | this._afterAll = []; 4230 | this.root = !title; 4231 | this._timeout = 2000; 4232 | this._slow = 75; 4233 | this._bail = false; 4234 | } 4235 | 4236 | /** 4237 | * Inherit from `EventEmitter.prototype`. 4238 | */ 4239 | 4240 | Suite.prototype = new EventEmitter; 4241 | Suite.prototype.constructor = Suite; 4242 | 4243 | 4244 | /** 4245 | * Return a clone of this `Suite`. 4246 | * 4247 | * @return {Suite} 4248 | * @api private 4249 | */ 4250 | 4251 | Suite.prototype.clone = function(){ 4252 | var suite = new Suite(this.title); 4253 | debug('clone'); 4254 | suite.ctx = this.ctx; 4255 | suite.timeout(this.timeout()); 4256 | suite.slow(this.slow()); 4257 | suite.bail(this.bail()); 4258 | return suite; 4259 | }; 4260 | 4261 | /** 4262 | * Set timeout `ms` or short-hand such as "2s". 4263 | * 4264 | * @param {Number|String} ms 4265 | * @return {Suite|Number} for chaining 4266 | * @api private 4267 | */ 4268 | 4269 | Suite.prototype.timeout = function(ms){ 4270 | if (0 == arguments.length) return this._timeout; 4271 | if ('string' == typeof ms) ms = milliseconds(ms); 4272 | debug('timeout %d', ms); 4273 | this._timeout = parseInt(ms, 10); 4274 | return this; 4275 | }; 4276 | 4277 | /** 4278 | * Set slow `ms` or short-hand such as "2s". 4279 | * 4280 | * @param {Number|String} ms 4281 | * @return {Suite|Number} for chaining 4282 | * @api private 4283 | */ 4284 | 4285 | Suite.prototype.slow = function(ms){ 4286 | if (0 === arguments.length) return this._slow; 4287 | if ('string' == typeof ms) ms = milliseconds(ms); 4288 | debug('slow %d', ms); 4289 | this._slow = ms; 4290 | return this; 4291 | }; 4292 | 4293 | /** 4294 | * Sets whether to bail after first error. 4295 | * 4296 | * @parma {Boolean} bail 4297 | * @return {Suite|Number} for chaining 4298 | * @api private 4299 | */ 4300 | 4301 | Suite.prototype.bail = function(bail){ 4302 | if (0 == arguments.length) return this._bail; 4303 | debug('bail %s', bail); 4304 | this._bail = bail; 4305 | return this; 4306 | }; 4307 | 4308 | /** 4309 | * Run `fn(test[, done])` before running tests. 4310 | * 4311 | * @param {Function} fn 4312 | * @return {Suite} for chaining 4313 | * @api private 4314 | */ 4315 | 4316 | Suite.prototype.beforeAll = function(fn){ 4317 | if (this.pending) return this; 4318 | var hook = new Hook('"before all" hook', fn); 4319 | hook.parent = this; 4320 | hook.timeout(this.timeout()); 4321 | hook.slow(this.slow()); 4322 | hook.ctx = this.ctx; 4323 | this._beforeAll.push(hook); 4324 | this.emit('beforeAll', hook); 4325 | return this; 4326 | }; 4327 | 4328 | /** 4329 | * Run `fn(test[, done])` after running tests. 4330 | * 4331 | * @param {Function} fn 4332 | * @return {Suite} for chaining 4333 | * @api private 4334 | */ 4335 | 4336 | Suite.prototype.afterAll = function(fn){ 4337 | if (this.pending) return this; 4338 | var hook = new Hook('"after all" hook', fn); 4339 | hook.parent = this; 4340 | hook.timeout(this.timeout()); 4341 | hook.slow(this.slow()); 4342 | hook.ctx = this.ctx; 4343 | this._afterAll.push(hook); 4344 | this.emit('afterAll', hook); 4345 | return this; 4346 | }; 4347 | 4348 | /** 4349 | * Run `fn(test[, done])` before each test case. 4350 | * 4351 | * @param {Function} fn 4352 | * @return {Suite} for chaining 4353 | * @api private 4354 | */ 4355 | 4356 | Suite.prototype.beforeEach = function(fn){ 4357 | if (this.pending) return this; 4358 | var hook = new Hook('"before each" hook', fn); 4359 | hook.parent = this; 4360 | hook.timeout(this.timeout()); 4361 | hook.slow(this.slow()); 4362 | hook.ctx = this.ctx; 4363 | this._beforeEach.push(hook); 4364 | this.emit('beforeEach', hook); 4365 | return this; 4366 | }; 4367 | 4368 | /** 4369 | * Run `fn(test[, done])` after each test case. 4370 | * 4371 | * @param {Function} fn 4372 | * @return {Suite} for chaining 4373 | * @api private 4374 | */ 4375 | 4376 | Suite.prototype.afterEach = function(fn){ 4377 | if (this.pending) return this; 4378 | var hook = new Hook('"after each" hook', fn); 4379 | hook.parent = this; 4380 | hook.timeout(this.timeout()); 4381 | hook.slow(this.slow()); 4382 | hook.ctx = this.ctx; 4383 | this._afterEach.push(hook); 4384 | this.emit('afterEach', hook); 4385 | return this; 4386 | }; 4387 | 4388 | /** 4389 | * Add a test `suite`. 4390 | * 4391 | * @param {Suite} suite 4392 | * @return {Suite} for chaining 4393 | * @api private 4394 | */ 4395 | 4396 | Suite.prototype.addSuite = function(suite){ 4397 | suite.parent = this; 4398 | suite.timeout(this.timeout()); 4399 | suite.slow(this.slow()); 4400 | suite.bail(this.bail()); 4401 | this.suites.push(suite); 4402 | this.emit('suite', suite); 4403 | return this; 4404 | }; 4405 | 4406 | /** 4407 | * Add a `test` to this suite. 4408 | * 4409 | * @param {Test} test 4410 | * @return {Suite} for chaining 4411 | * @api private 4412 | */ 4413 | 4414 | Suite.prototype.addTest = function(test){ 4415 | test.parent = this; 4416 | test.timeout(this.timeout()); 4417 | test.slow(this.slow()); 4418 | test.ctx = this.ctx; 4419 | this.tests.push(test); 4420 | this.emit('test', test); 4421 | return this; 4422 | }; 4423 | 4424 | /** 4425 | * Return the full title generated by recursively 4426 | * concatenating the parent's full title. 4427 | * 4428 | * @return {String} 4429 | * @api public 4430 | */ 4431 | 4432 | Suite.prototype.fullTitle = function(){ 4433 | if (this.parent) { 4434 | var full = this.parent.fullTitle(); 4435 | if (full) return full + ' ' + this.title; 4436 | } 4437 | return this.title; 4438 | }; 4439 | 4440 | /** 4441 | * Return the total number of tests. 4442 | * 4443 | * @return {Number} 4444 | * @api public 4445 | */ 4446 | 4447 | Suite.prototype.total = function(){ 4448 | return utils.reduce(this.suites, function(sum, suite){ 4449 | return sum + suite.total(); 4450 | }, 0) + this.tests.length; 4451 | }; 4452 | 4453 | /** 4454 | * Iterates through each suite recursively to find 4455 | * all tests. Applies a function in the format 4456 | * `fn(test)`. 4457 | * 4458 | * @param {Function} fn 4459 | * @return {Suite} 4460 | * @api private 4461 | */ 4462 | 4463 | Suite.prototype.eachTest = function(fn){ 4464 | utils.forEach(this.tests, fn); 4465 | utils.forEach(this.suites, function(suite){ 4466 | suite.eachTest(fn); 4467 | }); 4468 | return this; 4469 | }; 4470 | 4471 | }); // module: suite.js 4472 | 4473 | require.register("test.js", function(module, exports, require){ 4474 | 4475 | /** 4476 | * Module dependencies. 4477 | */ 4478 | 4479 | var Runnable = require('./runnable'); 4480 | 4481 | /** 4482 | * Expose `Test`. 4483 | */ 4484 | 4485 | module.exports = Test; 4486 | 4487 | /** 4488 | * Initialize a new `Test` with the given `title` and callback `fn`. 4489 | * 4490 | * @param {String} title 4491 | * @param {Function} fn 4492 | * @api private 4493 | */ 4494 | 4495 | function Test(title, fn) { 4496 | Runnable.call(this, title, fn); 4497 | this.pending = !fn; 4498 | this.type = 'test'; 4499 | } 4500 | 4501 | /** 4502 | * Inherit from `Runnable.prototype`. 4503 | */ 4504 | 4505 | Test.prototype = new Runnable; 4506 | Test.prototype.constructor = Test; 4507 | 4508 | 4509 | }); // module: test.js 4510 | 4511 | require.register("utils.js", function(module, exports, require){ 4512 | 4513 | /** 4514 | * Module dependencies. 4515 | */ 4516 | 4517 | var fs = require('browser/fs') 4518 | , path = require('browser/path') 4519 | , join = path.join 4520 | , debug = require('browser/debug')('mocha:watch'); 4521 | 4522 | /** 4523 | * Ignored directories. 4524 | */ 4525 | 4526 | var ignore = ['node_modules', '.git']; 4527 | 4528 | /** 4529 | * Escape special characters in the given string of html. 4530 | * 4531 | * @param {String} html 4532 | * @return {String} 4533 | * @api private 4534 | */ 4535 | 4536 | exports.escape = function(html){ 4537 | return String(html) 4538 | .replace(/&/g, '&') 4539 | .replace(/"/g, '"') 4540 | .replace(//g, '>'); 4542 | }; 4543 | 4544 | /** 4545 | * Array#forEach (<=IE8) 4546 | * 4547 | * @param {Array} array 4548 | * @param {Function} fn 4549 | * @param {Object} scope 4550 | * @api private 4551 | */ 4552 | 4553 | exports.forEach = function(arr, fn, scope){ 4554 | for (var i = 0, l = arr.length; i < l; i++) 4555 | fn.call(scope, arr[i], i); 4556 | }; 4557 | 4558 | /** 4559 | * Array#indexOf (<=IE8) 4560 | * 4561 | * @parma {Array} arr 4562 | * @param {Object} obj to find index of 4563 | * @param {Number} start 4564 | * @api private 4565 | */ 4566 | 4567 | exports.indexOf = function(arr, obj, start){ 4568 | for (var i = start || 0, l = arr.length; i < l; i++) { 4569 | if (arr[i] === obj) 4570 | return i; 4571 | } 4572 | return -1; 4573 | }; 4574 | 4575 | /** 4576 | * Array#reduce (<=IE8) 4577 | * 4578 | * @param {Array} array 4579 | * @param {Function} fn 4580 | * @param {Object} initial value 4581 | * @api private 4582 | */ 4583 | 4584 | exports.reduce = function(arr, fn, val){ 4585 | var rval = val; 4586 | 4587 | for (var i = 0, l = arr.length; i < l; i++) { 4588 | rval = fn(rval, arr[i], i, arr); 4589 | } 4590 | 4591 | return rval; 4592 | }; 4593 | 4594 | /** 4595 | * Array#filter (<=IE8) 4596 | * 4597 | * @param {Array} array 4598 | * @param {Function} fn 4599 | * @api private 4600 | */ 4601 | 4602 | exports.filter = function(arr, fn){ 4603 | var ret = []; 4604 | 4605 | for (var i = 0, l = arr.length; i < l; i++) { 4606 | var val = arr[i]; 4607 | if (fn(val, i, arr)) ret.push(val); 4608 | } 4609 | 4610 | return ret; 4611 | }; 4612 | 4613 | /** 4614 | * Object.keys (<=IE8) 4615 | * 4616 | * @param {Object} obj 4617 | * @return {Array} keys 4618 | * @api private 4619 | */ 4620 | 4621 | exports.keys = Object.keys || function(obj) { 4622 | var keys = [] 4623 | , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 4624 | 4625 | for (var key in obj) { 4626 | if (has.call(obj, key)) { 4627 | keys.push(key); 4628 | } 4629 | } 4630 | 4631 | return keys; 4632 | }; 4633 | 4634 | /** 4635 | * Watch the given `files` for changes 4636 | * and invoke `fn(file)` on modification. 4637 | * 4638 | * @param {Array} files 4639 | * @param {Function} fn 4640 | * @api private 4641 | */ 4642 | 4643 | exports.watch = function(files, fn){ 4644 | var options = { interval: 100 }; 4645 | files.forEach(function(file){ 4646 | debug('file %s', file); 4647 | fs.watchFile(file, options, function(curr, prev){ 4648 | if (prev.mtime < curr.mtime) fn(file); 4649 | }); 4650 | }); 4651 | }; 4652 | 4653 | /** 4654 | * Ignored files. 4655 | */ 4656 | 4657 | function ignored(path){ 4658 | return !~ignore.indexOf(path); 4659 | } 4660 | 4661 | /** 4662 | * Lookup files in the given `dir`. 4663 | * 4664 | * @return {Array} 4665 | * @api private 4666 | */ 4667 | 4668 | exports.files = function(dir, ret){ 4669 | ret = ret || []; 4670 | 4671 | fs.readdirSync(dir) 4672 | .filter(ignored) 4673 | .forEach(function(path){ 4674 | path = join(dir, path); 4675 | if (fs.statSync(path).isDirectory()) { 4676 | exports.files(path, ret); 4677 | } else if (path.match(/\.(js|coffee)$/)) { 4678 | ret.push(path); 4679 | } 4680 | }); 4681 | 4682 | return ret; 4683 | }; 4684 | 4685 | /** 4686 | * Compute a slug from the given `str`. 4687 | * 4688 | * @param {String} str 4689 | * @return {String} 4690 | * @api private 4691 | */ 4692 | 4693 | exports.slug = function(str){ 4694 | return str 4695 | .toLowerCase() 4696 | .replace(/ +/g, '-') 4697 | .replace(/[^-\w]/g, ''); 4698 | }; 4699 | 4700 | /** 4701 | * Strip the function definition from `str`, 4702 | * and re-indent for pre whitespace. 4703 | */ 4704 | 4705 | exports.clean = function(str) { 4706 | str = str 4707 | .replace(/^function *\(.*\) *{/, '') 4708 | .replace(/\s+\}$/, ''); 4709 | 4710 | var spaces = str.match(/^\n?( *)/)[1].length 4711 | , re = new RegExp('^ {' + spaces + '}', 'gm'); 4712 | 4713 | str = str.replace(re, ''); 4714 | 4715 | return exports.trim(str); 4716 | }; 4717 | 4718 | /** 4719 | * Escape regular expression characters in `str`. 4720 | * 4721 | * @param {String} str 4722 | * @return {String} 4723 | * @api private 4724 | */ 4725 | 4726 | exports.escapeRegexp = function(str){ 4727 | return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); 4728 | }; 4729 | 4730 | /** 4731 | * Trim the given `str`. 4732 | * 4733 | * @param {String} str 4734 | * @return {String} 4735 | * @api private 4736 | */ 4737 | 4738 | exports.trim = function(str){ 4739 | return str.replace(/^\s+|\s+$/g, ''); 4740 | }; 4741 | 4742 | /** 4743 | * Parse the given `qs`. 4744 | * 4745 | * @param {String} qs 4746 | * @return {Object} 4747 | * @api private 4748 | */ 4749 | 4750 | exports.parseQuery = function(qs){ 4751 | return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){ 4752 | var i = pair.indexOf('=') 4753 | , key = pair.slice(0, i) 4754 | , val = pair.slice(++i); 4755 | 4756 | obj[key] = decodeURIComponent(val); 4757 | return obj; 4758 | }, {}); 4759 | }; 4760 | 4761 | /** 4762 | * Highlight the given string of `js`. 4763 | * 4764 | * @param {String} js 4765 | * @return {String} 4766 | * @api private 4767 | */ 4768 | 4769 | function highlight(js) { 4770 | return js 4771 | .replace(//g, '>') 4773 | .replace(/\/\/(.*)/gm, '//$1') 4774 | .replace(/('.*?')/gm, '$1') 4775 | .replace(/(\d+\.\d+)/gm, '$1') 4776 | .replace(/(\d+)/gm, '$1') 4777 | .replace(/\bnew *(\w+)/gm, 'new $1') 4778 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') 4779 | } 4780 | 4781 | /** 4782 | * Highlight the contents of tag `name`. 4783 | * 4784 | * @param {String} name 4785 | * @api private 4786 | */ 4787 | 4788 | exports.highlightTags = function(name) { 4789 | var code = document.getElementsByTagName(name); 4790 | for (var i = 0, len = code.length; i < len; ++i) { 4791 | code[i].innerHTML = highlight(code[i].innerHTML); 4792 | } 4793 | }; 4794 | 4795 | }); // module: utils.js 4796 | /** 4797 | * Node shims. 4798 | * 4799 | * These are meant only to allow 4800 | * mocha.js to run untouched, not 4801 | * to allow running node code in 4802 | * the browser. 4803 | */ 4804 | 4805 | process = {}; 4806 | process.exit = function(status){}; 4807 | process.stdout = {}; 4808 | global = window; 4809 | 4810 | /** 4811 | * next tick implementation. 4812 | */ 4813 | 4814 | process.nextTick = (function(){ 4815 | // postMessage behaves badly on IE8 4816 | if (window.ActiveXObject || !window.postMessage) { 4817 | return function(fn){ fn() }; 4818 | } 4819 | 4820 | // based on setZeroTimeout by David Baron 4821 | // - http://dbaron.org/log/20100309-faster-timeouts 4822 | var timeouts = [] 4823 | , name = 'mocha-zero-timeout' 4824 | 4825 | window.addEventListener('message', function(e){ 4826 | if (e.source == window && e.data == name) { 4827 | if (e.stopPropagation) e.stopPropagation(); 4828 | if (timeouts.length) timeouts.shift()(); 4829 | } 4830 | }, true); 4831 | 4832 | return function(fn){ 4833 | timeouts.push(fn); 4834 | window.postMessage(name, '*'); 4835 | } 4836 | })(); 4837 | 4838 | /** 4839 | * Remove uncaughtException listener. 4840 | */ 4841 | 4842 | process.removeListener = function(e){ 4843 | if ('uncaughtException' == e) { 4844 | window.onerror = null; 4845 | } 4846 | }; 4847 | 4848 | /** 4849 | * Implements uncaughtException listener. 4850 | */ 4851 | 4852 | process.on = function(e, fn){ 4853 | if ('uncaughtException' == e) { 4854 | window.onerror = fn; 4855 | } 4856 | }; 4857 | 4858 | // boot 4859 | ;(function(){ 4860 | 4861 | /** 4862 | * Expose mocha. 4863 | */ 4864 | 4865 | var Mocha = window.Mocha = require('mocha'), 4866 | mocha = window.mocha = new Mocha({ reporter: 'html' }); 4867 | 4868 | /** 4869 | * Override ui to ensure that the ui functions are initialized. 4870 | * Normally this would happen in Mocha.prototype.loadFiles. 4871 | */ 4872 | 4873 | mocha.ui = function(ui){ 4874 | Mocha.prototype.ui.call(this, ui); 4875 | this.suite.emit('pre-require', window, null, this); 4876 | return this; 4877 | }; 4878 | 4879 | /** 4880 | * Setup mocha with the given setting options. 4881 | */ 4882 | 4883 | mocha.setup = function(opts){ 4884 | if ('string' == typeof opts) opts = { ui: opts }; 4885 | for (var opt in opts) this[opt](opts[opt]); 4886 | return this; 4887 | }; 4888 | 4889 | /** 4890 | * Run mocha, returning the Runner. 4891 | */ 4892 | 4893 | mocha.run = function(fn){ 4894 | var options = mocha.options; 4895 | mocha.globals('location'); 4896 | 4897 | var query = Mocha.utils.parseQuery(window.location.search || ''); 4898 | if (query.grep) mocha.grep(query.grep); 4899 | 4900 | return Mocha.prototype.run.call(mocha, function(){ 4901 | Mocha.utils.highlightTags('code'); 4902 | if (fn) fn(); 4903 | }); 4904 | }; 4905 | })(); 4906 | })(); -------------------------------------------------------------------------------- /test/model.js: -------------------------------------------------------------------------------- 1 | 2 | try { 3 | var model = require('model'); 4 | } catch (e) { 5 | var model = require('..'); 6 | } 7 | 8 | var assert = require('assert'); 9 | var request = require('superagent'); 10 | 11 | var User = model('User') 12 | .attr('id', { type: 'number' }) 13 | .attr('name', { type: 'string' }) 14 | .attr('age', { type: 'number' }) 15 | .headers({'X-API-TOKEN': 'token string'}); 16 | 17 | function required(attr) { 18 | return function(Model){ 19 | Model.validate(function(model){ 20 | if (!model.has(attr)) model.error(attr, 'field required'); 21 | }); 22 | } 23 | } 24 | 25 | var Pet = model('Pet') 26 | .attr('id') 27 | .attr('name') 28 | .attr('species') 29 | .use(required('name')) 30 | .headers({'X-API-TOKEN': 'token string'}); 31 | 32 | function reset(fn) { 33 | request.del('/', function(res){ 34 | fn(); 35 | }); 36 | } 37 | 38 | describe('model(name)', function(){ 39 | it('should return a new model constructor', function(){ 40 | var Something = model('Something'); 41 | assert('function' == typeof Something); 42 | }) 43 | }) 44 | 45 | describe('new Model(object)', function(){ 46 | it('should populate attrs', function(){ 47 | var user = new User({ name: 'Tobi', age: 2 }); 48 | assert('Tobi' == user.name()); 49 | assert(2 == user.age()); 50 | }) 51 | 52 | it('should emit "construct" event', function(done){ 53 | User.on('construct', function(user, attrs){ 54 | assert('Tobi' == user.name()); 55 | assert('Tobi' == attrs.name); 56 | User.off('construct'); 57 | done(); 58 | }); 59 | new User({ name: 'Tobi' }); 60 | }) 61 | }) 62 | 63 | describe('Model(object)', function(){ 64 | it('should populate attrs', function(){ 65 | var user = User({ name: 'Tobi', age: 2 }); 66 | assert('Tobi' == user.name()); 67 | assert(2 == user.age()); 68 | }) 69 | }) 70 | 71 | describe('Model#.(value)', function(){ 72 | it('should set a value', function(){ 73 | var user = new User; 74 | assert(user == user.name('Tobi')); 75 | assert('Tobi' == user.name()); 76 | }) 77 | 78 | it('should emit "change " events', function(done){ 79 | var user = new User({ name: 'Tobi' }); 80 | 81 | user.on('change name', function(val, old){ 82 | assert('Luna' == val); 83 | assert('Tobi' == old); 84 | done(); 85 | }); 86 | 87 | user.name('Luna'); 88 | }) 89 | 90 | it('should emit "change" events', function(done){ 91 | var user = new User({ name: 'Tobi' }); 92 | 93 | user.on('change', function(prop, val, old){ 94 | assert('name' == prop); 95 | assert('Luna' == val); 96 | assert('Tobi' == old); 97 | done(); 98 | }); 99 | 100 | user.name('Luna'); 101 | }) 102 | }) 103 | 104 | describe('Model#isNew()', function(){ 105 | it('should default to true', function(){ 106 | var user = new User; 107 | assert(true === user.isNew()); 108 | }) 109 | 110 | it('should be false when a primary key is present', function(){ 111 | var user = new User({ id: 0 }); 112 | assert(false === user.isNew()); 113 | }) 114 | }) 115 | 116 | describe('Model#model', function(){ 117 | it('should reference the constructor', function(){ 118 | var user = new User; 119 | assert(User == user.model); 120 | 121 | var pet = new Pet; 122 | assert(Pet == pet.model); 123 | }) 124 | }) 125 | 126 | describe('Model#set(attrs)', function(){ 127 | it('should set several attrs', function(){ 128 | var user = new User; 129 | user.set({ name: 'Tobi', age: 2 }); 130 | assert('Tobi' == user.name()); 131 | assert(2 == user.age()); 132 | }) 133 | }) 134 | 135 | describe('Model#get(attr)', function(){ 136 | it('should return an attr value', function(){ 137 | var user = new User({ name: 'Tobi' }); 138 | assert('Tobi' == user.get('name')); 139 | }) 140 | }) 141 | 142 | describe('Model#has(attr)', function(){ 143 | it('should check if attr is not null or undefined', function(){ 144 | var user = new User({ name: 'Tobi' }); 145 | assert(true === user.has('name')); 146 | assert(false === user.has('age')); 147 | }) 148 | }) 149 | 150 | describe('Model#destroy()', function(){ 151 | describe('when new', function(){ 152 | it('should error', function(done){ 153 | var pet = new Pet; 154 | pet.destroy(function(err){ 155 | assert('not saved' == err.message); 156 | done(); 157 | }); 158 | }) 159 | }) 160 | 161 | describe('when old', function(){ 162 | it('should DEL /:model/:id', function(done){ 163 | var pet = new Pet({ name: 'Tobi' }); 164 | pet.save(function(err){ 165 | assert(!err); 166 | pet.destroy(function(err){ 167 | assert(!err); 168 | assert(pet.destroyed); 169 | done(); 170 | }); 171 | }); 172 | }) 173 | 174 | it('should emit "destroy"', function(done){ 175 | var pet = new Pet({ name: 'Tobi' }); 176 | pet.save(function(err){ 177 | assert(!err); 178 | pet.on('destroy', done); 179 | pet.destroy(); 180 | }); 181 | }) 182 | 183 | it('should emit "destroying" on the constructor', function(done){ 184 | var pet = new Pet({ name: 'Tobi' }); 185 | pet.save(function(err){ 186 | assert(!err); 187 | Pet.once('destroying', function(obj){ 188 | assert(pet == obj); 189 | done(); 190 | }); 191 | pet.destroy(); 192 | }); 193 | }) 194 | 195 | it('should emit "destroy"', function(done){ 196 | var pet = new Pet({ name: 'Tobi' }); 197 | pet.save(function(err){ 198 | assert(!err); 199 | pet.on('destroy', done); 200 | pet.destroy(); 201 | }); 202 | }) 203 | 204 | it('should emit "destroy" on the constructor', function(done){ 205 | var pet = new Pet({ name: 'Tobi' }); 206 | pet.save(function(err){ 207 | assert(!err); 208 | Pet.once('destroy', function(obj){ 209 | assert(pet == obj); 210 | done(); 211 | }); 212 | pet.destroy(); 213 | }); 214 | }) 215 | 216 | it('should emit "destroy" and pass response', function(done){ 217 | var pet = new Pet({ name: 'Tobi' }); 218 | pet.save(function(err){ 219 | assert(!err); 220 | Pet.once('destroy', function(obj, res){ 221 | assert(pet == obj); 222 | assert(res); 223 | assert(res.req.header['X-API-TOKEN'] == 'token string'); 224 | done(); 225 | }); 226 | pet.destroy(); 227 | }); 228 | }); 229 | }) 230 | }) 231 | 232 | describe('Model#save(fn)', function(){ 233 | beforeEach(reset); 234 | 235 | describe('when new', function(){ 236 | describe('and valid', function(){ 237 | it('should POST to /:model', function(done){ 238 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 239 | pet.save(function(){ 240 | assert(0 == pet.id()); 241 | done(); 242 | }); 243 | }) 244 | 245 | it('should emit "saving"', function(done){ 246 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 247 | pet.on('saving', function(){ 248 | assert(pet.isNew()); 249 | done(); 250 | }); 251 | pet.save(); 252 | }) 253 | 254 | it('should emit "saving" on the constructor', function(done){ 255 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 256 | Pet.once('saving', function(obj){ 257 | assert(pet == obj); 258 | done(); 259 | }); 260 | pet.save(); 261 | }) 262 | 263 | it('should emit "save"', function(done){ 264 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 265 | pet.on('save', done); 266 | pet.save(); 267 | }) 268 | 269 | it('should emit "save" on the constructor', function(done){ 270 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 271 | Pet.once('save', function(obj){ 272 | assert(pet == obj); 273 | done(); 274 | }); 275 | pet.save(); 276 | }) 277 | }) 278 | 279 | describe('and invalid', function(){ 280 | it('should error', function(done){ 281 | var pet = new Pet; 282 | pet.save(function(err){ 283 | assert('validation failed' == err.message); 284 | assert(1 == pet.errors.length); 285 | assert('name' == pet.errors[0].attr); 286 | assert('field required' == pet.errors[0].message); 287 | assert(null == pet.id()); 288 | done(); 289 | }); 290 | }) 291 | }) 292 | }) 293 | 294 | describe('when old', function(){ 295 | describe('and valid', function(){ 296 | it('should PUT to /:model/:id', function(done){ 297 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 298 | pet.save(function(){ 299 | assert(0 == pet.id()); 300 | pet.name('Loki'); 301 | pet.save(function(){ 302 | assert(0 == pet.id()); 303 | Pet.get(0, function(err, pet){ 304 | assert(0 == pet.id()); 305 | assert('Loki' == pet.name()); 306 | done(); 307 | }); 308 | }); 309 | }); 310 | }) 311 | 312 | it('should emit "saving"', function(done){ 313 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 314 | pet.save(function(err){ 315 | assert(!err); 316 | pet.on('saving', done); 317 | pet.save(); 318 | }); 319 | }) 320 | 321 | it('should emit "saving" on the constructor', function(done){ 322 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 323 | pet.save(function(){ 324 | Pet.once('saving', function(obj){ 325 | assert(pet == obj); 326 | done(); 327 | }); 328 | pet.save(); 329 | }); 330 | }) 331 | 332 | it('should emit "save"', function(done){ 333 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 334 | pet.save(function(err){ 335 | assert(!err); 336 | pet.on('save', done); 337 | pet.save(); 338 | }); 339 | }) 340 | 341 | it('should emit "save" on the constructor', function(done){ 342 | var pet = new Pet({ name: 'Tobi', species: 'Ferret' }); 343 | pet.save(function(err){ 344 | assert(!err); 345 | Pet.once('save', function(obj){ 346 | assert(pet == obj); 347 | done(); 348 | }); 349 | pet.save(); 350 | }); 351 | }) 352 | }) 353 | 354 | describe('and invalid', function(){ 355 | it('should error', function(done){ 356 | var pet = new Pet({ name: 'Tobi' }); 357 | pet.save(function(err){ 358 | assert(!err); 359 | pet.name(null); 360 | pet.save(function(err){ 361 | assert('validation failed' == err.message); 362 | assert(1 == pet.errors.length); 363 | assert('name' == pet.errors[0].attr); 364 | assert('field required' == pet.errors[0].message); 365 | assert(0 == pet.id()); 366 | done(); 367 | }); 368 | }); 369 | }) 370 | }) 371 | 372 | it('should have headers and return res object', function(done){ 373 | var pet = new Pet({ name: 'Tobi' }); 374 | pet.save(function(err, res){ 375 | assert(!err); 376 | assert(res); 377 | assert(res.req.header['X-API-TOKEN'] == 'token string'); 378 | pet.name(null); 379 | pet.save(function(err, res){ 380 | assert(err); 381 | assert(!res); // vadiation error req never made 382 | done(); 383 | }); 384 | }) 385 | }) 386 | }) 387 | }) 388 | 389 | describe('Model#url(path)', function(){ 390 | it('should include .id', function(){ 391 | var user = new User; 392 | user.id(5); 393 | assert('/users/5' == user.url()); 394 | assert('/users/5/edit' == user.url('edit')); 395 | }) 396 | }) 397 | 398 | describe('Model#toJSON()', function(){ 399 | it('should return the attributes', function(){ 400 | var user = new User({ name: 'Tobi', age: 2 }); 401 | var obj = user.toJSON(); 402 | assert('Tobi' == obj.name); 403 | assert(2 == obj.age); 404 | }) 405 | }) 406 | 407 | describe('Model#isValid()', function(){ 408 | var User = model('User') 409 | .attr('name') 410 | .attr('email'); 411 | 412 | User.validate(function(user){ 413 | if (!user.has('name')) user.error('name', 'name is required'); 414 | }); 415 | 416 | User.validate(function(user){ 417 | if (!user.has('email')) user.error('email', 'email is required'); 418 | }); 419 | 420 | it('should populate .errors', function(){ 421 | var user = new User; 422 | assert(false === user.isValid()); 423 | assert(2 == user.errors.length); 424 | assert('name' == user.errors[0].attr); 425 | assert('name is required' == user.errors[0].message); 426 | assert('email' == user.errors[1].attr); 427 | assert('email is required' == user.errors[1].message); 428 | }) 429 | 430 | it('should return false until valid', function(){ 431 | var user = new User; 432 | assert(false === user.isValid()); 433 | assert(2 == user.errors.length); 434 | 435 | user.name('Tobi'); 436 | assert(false === user.isValid()); 437 | assert(1 == user.errors.length); 438 | 439 | user.email('tobi@learnboost.com'); 440 | assert(true === user.isValid()); 441 | assert(0 == user.errors.length); 442 | }) 443 | }) 444 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express'); 7 | var app = express(); 8 | 9 | // middleware 10 | 11 | app.use(express.logger('dev')); 12 | app.use(express.bodyParser()); 13 | app.use(express.static(__dirname)); 14 | app.use(express.static(__dirname + '/..')); 15 | 16 | // faux db 17 | 18 | var db = { pets: [], users: [] }; 19 | 20 | // routes 21 | 22 | /** 23 | * DELETE everythinggggg. 24 | */ 25 | 26 | app.del('/', function(req, res){ 27 | db.pets = []; 28 | db.users = []; 29 | res.send(200); 30 | }); 31 | 32 | /** 33 | * GET pet :id. 34 | */ 35 | 36 | app.get('/pets/:id', function(req, res){ 37 | var pet = db.pets[req.params.id]; 38 | if (!pet) return res.send(404, 'cant find pet'); 39 | res.send(pet); 40 | }); 41 | 42 | /** 43 | * POST to create a new pet. 44 | */ 45 | 46 | app.post('/pets', function(req, res){ 47 | var pet = req.body; 48 | pet.id = db.pets.push(pet) - 1; 49 | res.send(pet); 50 | }); 51 | 52 | /** 53 | * PUT to update pet :id. 54 | */ 55 | 56 | app.put('/pets/:id', function(req, res){ 57 | var pet = db.pets[req.params.id]; 58 | if (!pet) return res.send(404, 'cant find pet'); 59 | db.pets[pet.id] = req.body; 60 | res.send(200); 61 | }); 62 | 63 | /** 64 | * DELETE pet :id. 65 | */ 66 | 67 | app.del('/pets/:id', function(req, res){ 68 | var pet = db.pets[req.params.id]; 69 | if (!pet) return res.send(404, 'cant find pet'); 70 | db.pets.splice(pet.id, 1); 71 | res.send(200); 72 | }); 73 | 74 | // users 75 | 76 | /** 77 | * DELETE all users. 78 | */ 79 | 80 | app.del('/users', function(req, res){ 81 | db.users = []; 82 | res.send(200); 83 | }); 84 | 85 | /** 86 | * GET all users. 87 | */ 88 | 89 | app.get('/users', function(req, res){ 90 | res.send(db.users); 91 | }); 92 | 93 | /** 94 | * POST a new user. 95 | */ 96 | 97 | app.post('/users', function(req, res){ 98 | var user = req.body; 99 | var id = db.users.push(user) - 1; 100 | user.id = id; 101 | res.send({ id: id }); 102 | }); 103 | 104 | app.listen(4000); 105 | console.log('test server listening on port 4000'); 106 | -------------------------------------------------------------------------------- /test/statics.js: -------------------------------------------------------------------------------- 1 | 2 | try { 3 | var model = require('model'); 4 | } catch (e) { 5 | var model = require('..'); 6 | } 7 | 8 | var assert = require('assert'); 9 | 10 | var User = model('User') 11 | .attr('id', { type: 'number' }) 12 | .attr('name', { type: 'string' }) 13 | .attr('age', { type: 'number' }) 14 | .headers({'X-API-TOKEN': 'token string'}) 15 | 16 | describe('Model.url()', function(){ 17 | it('should return the base url', function(){ 18 | assert('/users' == User.url()); 19 | }) 20 | }) 21 | 22 | describe('Model.url(string)', function(){ 23 | it('should join', function(){ 24 | assert('/users/edit' == User.url('edit')); 25 | }) 26 | }) 27 | 28 | describe('Model.attrs', function(){ 29 | it('should hold the defined attrs', function(){ 30 | assert('string' == User.attrs.name.type); 31 | assert('number' == User.attrs.age.type); 32 | }) 33 | }) 34 | 35 | describe('Model.all(fn)', function(){ 36 | beforeEach(function(done){ 37 | User.destroyAll(done); 38 | }); 39 | 40 | beforeEach(function(done){ 41 | var tobi = new User({ name: 'tobi', age: 2 }); 42 | var loki = new User({ name: 'loki', age: 1 }); 43 | var jane = new User({ name: 'jane', age: 8 }); 44 | tobi.save(function(){ 45 | loki.save(function(){ 46 | jane.save(done); 47 | }); 48 | }); 49 | }) 50 | 51 | it('should respond with a collection of all', function(done){ 52 | User.all(function(err, users, res){ 53 | assert(!err); 54 | assert(res); 55 | assert(res.req.header['X-API-TOKEN'] == 'token string') 56 | assert(3 == users.length()); 57 | assert('tobi' == users.at(0).name()); 58 | assert('loki' == users.at(1).name()); 59 | assert('jane' == users.at(2).name()); 60 | done(); 61 | }); 62 | }) 63 | }) 64 | 65 | describe('Model.route(string)', function(){ 66 | it('should set the base path for url', function(){ 67 | User.route('/api/u'); 68 | assert('/api/u/edit' == User.url('edit')); 69 | }) 70 | }) 71 | --------------------------------------------------------------------------------