├── .gitignore ├── test ├── tests.html ├── test.js ├── mocha.css └── mocha.js ├── package.json ├── LICENSE ├── Readme.md └── lib └── connect-sqlite3.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X automatic create files 2 | .DS_Store 3 | 4 | # Vim automatic create files 5 | *~ 6 | .*.sw* 7 | 8 | # Static, Temporary, Content files 9 | *.db 10 | 11 | # npm modules 12 | node_modules 13 | 14 | -------------------------------------------------------------------------------- /test/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mocha 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "connect-sqlite3", 3 | "description": "SQLite3 session store for Connect", 4 | "version": "0.9.16", 5 | "author": "David Feinberg", 6 | "main": "lib/connect-sqlite3", 7 | "scripts": { 8 | "test": "mocha test/test.js" 9 | }, 10 | "peerDependencies": { 11 | "sqlite3": "^5.0.0", 12 | "express-session": "^1.0.0" 13 | }, 14 | "devDependencies": { 15 | "connect": ">=1.0.0", 16 | "express-session": "^1.18.1", 17 | "mocha": "^2.3.4", 18 | "should": "^8.2.0", 19 | "sqlite3": "^5.1.7" 20 | }, 21 | "engines": { 22 | "node": ">=0.4.x" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git://github.com/rawberg/connect-sqlite3.git" 27 | }, 28 | "licenses": [ 29 | { 30 | "type": "MIT" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 David Feinberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Connect SQLite3 2 | 3 | connect-sqlite3 is a SQLite3 session store modeled after the TJ's connect-redis store. 4 | 5 | 6 | ## Installation 7 | ```sh 8 | $ npm install connect-sqlite3 9 | ``` 10 | 11 | ## Options 12 | 13 | - `table='sessions'` Database table name 14 | - `db=dbConnection` SQLite database connection object 15 | - `concurrentDB='false'` Enables [WAL](https://www.sqlite.org/wal.html) mode (defaults to false) 16 | 17 | > **Note:** The `options` parameter requires an already initialized database connection instead of a file name. This design allows the library to remain flexible about database connection management in your application. 18 | 19 | ## Usage 20 | ```js 21 | var connect = require('connect'), 22 | dbConnection = new sqlite3.Database(':memory:'), 23 | SQLiteStore = require('connect-sqlite3')(connect); 24 | 25 | connect.createServer( 26 | connect.cookieParser(), 27 | connect.session({ store: new SQLiteStore({ db: dbConnection }), secret: 'your secret' }) 28 | ); 29 | ``` 30 | with express 31 | ```js 32 | 3.x: 33 | var SQLiteStore = require('connect-sqlite3')(express); 34 | 35 | 4.x: 36 | var session = require('express-session'); 37 | var SQLiteStore = require('connect-sqlite3')(session); 38 | 39 | var dbConnection = new sqlite3.Database('./sessions.db') 40 | app.configure(function() { 41 | app.set('views', __dirname + '/views'); 42 | app.set('view engine', 'ejs'); 43 | app.use(express.bodyParser()); 44 | app.use(express.methodOverride()); 45 | app.use(express.cookieParser()); 46 | app.use(session({ 47 | store: new SQLiteStore({ db: dbConnection }), 48 | secret: 'your secret', 49 | cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } // 1 week 50 | })); 51 | app.use(app.router); 52 | app.use(express.static(__dirname + '/public')); 53 | }); 54 | ``` 55 | ## Test 56 | ```sh 57 | $ npm test 58 | ``` 59 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var should = require('should'), 2 | session = require('express-session'), 3 | sqlite3 = require('sqlite3'), 4 | util = require('util'), 5 | SQLiteStore = require('../lib/connect-sqlite3.js')(session); 6 | 7 | describe('connect-sqlite3 basic test suite', function() { 8 | var dbConnection; 9 | 10 | before(function() { 11 | dbConnection = new sqlite3.Database(':memory:'); 12 | this.memStore = new SQLiteStore({db: dbConnection}); 13 | }); 14 | 15 | after(function() { 16 | dbConnection.close(); 17 | }); 18 | 19 | it('it should save a new session record', function(done) { 20 | this.memStore.set('1111222233334444', {cookie: {maxAge:2000}, name: 'sample name'}, function(err, rows) { 21 | should.not.exist(err, 'set() returned an error'); 22 | rows.should.be.empty; 23 | done(); 24 | }); 25 | }); 26 | 27 | 28 | it('it should retrieve an active session', function(done) { 29 | this.memStore.get('1111222233334444', function(err, session) { 30 | should.not.exist(err, 'get() returned an error'); 31 | should.exist(session); 32 | (session).should.eql({cookie: {maxAge:2000}, name: 'sample name'}); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('it should gracefully handle retrieving an unkonwn session', function(done) { 38 | this.memStore.get('hope-and-change', function(err, rows) { 39 | should.not.exist(err, 'get() unknown session returned an error'); 40 | should.equal(undefined, rows, 'unknown session is not undefined'); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('it should only contain one session', function(done) { 46 | this.memStore.length(function(err, len) { 47 | should.not.exist(err, 'session count returned an error'); 48 | should.exist(len); 49 | len.should.equal(1); 50 | done(); 51 | }); 52 | }); 53 | 54 | it('it should clear all session records', function(done) { 55 | var that = this; 56 | this.memStore.clear(function(err, success) { 57 | should.not.exist(err, 'clear returned an error'); 58 | success.should.be.true; 59 | 60 | that.memStore.length(function(err, len) { 61 | should.not.exist(err, 'session count after clear returned an error'); 62 | should.exist(len); 63 | len.should.equal(0); 64 | done(); 65 | }); 66 | }); 67 | }); 68 | 69 | it('it should destroy a session', function(done) { 70 | var that = this; 71 | this.memStore.set('555666777', {cookie: {maxAge:1000}, name: 'Rob Dobilina'}, function(err, rows) { 72 | should.not.exist(err, 'set() returned an error'); 73 | rows.should.be.empty; 74 | 75 | that.memStore.destroy('555666777', function(err) { 76 | should.not.exist(err, 'destroy returned an error'); 77 | 78 | that.memStore.length(function(err, len) { 79 | should.not.exist(err, 'session count after destroy returned an error'); 80 | should.exist(len); 81 | len.should.equal(0); 82 | done(); 83 | }); 84 | }); 85 | }); 86 | }); 87 | }); 88 | 89 | -------------------------------------------------------------------------------- /test/mocha.css: -------------------------------------------------------------------------------- 1 | 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 | #mocha h2 { 41 | font-size: 12px; 42 | font-weight: normal; 43 | cursor: pointer; 44 | } 45 | 46 | #mocha .suite { 47 | margin-left: 15px; 48 | } 49 | 50 | #mocha .test { 51 | margin-left: 15px; 52 | } 53 | 54 | #mocha .test:hover h2::after { 55 | position: relative; 56 | top: 0; 57 | right: -10px; 58 | content: '(view source)'; 59 | font-size: 12px; 60 | font-family: arial; 61 | color: #888; 62 | } 63 | 64 | #mocha .test.pending:hover h2::after { 65 | content: '(pending)'; 66 | font-family: arial; 67 | } 68 | 69 | #mocha .test.pass.medium .duration { 70 | background: #C09853; 71 | } 72 | 73 | #mocha .test.pass.slow .duration { 74 | background: #B94A48; 75 | } 76 | 77 | #mocha .test.pass::before { 78 | content: '✓'; 79 | font-size: 12px; 80 | display: block; 81 | float: left; 82 | margin-right: 5px; 83 | color: #00d6b2; 84 | } 85 | 86 | #mocha .test.pass .duration { 87 | font-size: 9px; 88 | margin-left: 5px; 89 | padding: 2px 5px; 90 | color: white; 91 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 92 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 93 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -webkit-border-radius: 5px; 95 | -moz-border-radius: 5px; 96 | -ms-border-radius: 5px; 97 | -o-border-radius: 5px; 98 | border-radius: 5px; 99 | } 100 | 101 | #mocha .test.pass.fast .duration { 102 | display: none; 103 | } 104 | 105 | #mocha .test.pending { 106 | color: #0b97c4; 107 | } 108 | 109 | #mocha .test.pending::before { 110 | content: '◦'; 111 | color: #0b97c4; 112 | } 113 | 114 | #mocha .test.fail { 115 | color: #c00; 116 | } 117 | 118 | #mocha .test.fail pre { 119 | color: black; 120 | } 121 | 122 | #mocha .test.fail::before { 123 | content: '✖'; 124 | font-size: 12px; 125 | display: block; 126 | float: left; 127 | margin-right: 5px; 128 | color: #c00; 129 | } 130 | 131 | #mocha .test pre.error { 132 | color: #c00; 133 | } 134 | 135 | #mocha .test pre { 136 | display: inline-block; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | } 145 | 146 | #error { 147 | color: #c00; 148 | font-size: 1.5 em; 149 | font-weight: 100; 150 | letter-spacing: 1px; 151 | } 152 | 153 | #stats { 154 | position: fixed; 155 | top: 15px; 156 | right: 10px; 157 | font-size: 12px; 158 | margin: 0; 159 | color: #888; 160 | } 161 | 162 | #stats .progress { 163 | float: right; 164 | padding-top: 0; 165 | } 166 | 167 | #stats em { 168 | color: black; 169 | } 170 | 171 | #stats li { 172 | display: inline-block; 173 | margin: 0 5px; 174 | list-style: none; 175 | padding-top: 11px; 176 | } 177 | 178 | code .comment { color: #ddd } 179 | code .init { color: #2F6FAD } 180 | code .string { color: #5890AD } 181 | code .keyword { color: #8A6343 } 182 | code .number { color: #2F6FAD } 183 | -------------------------------------------------------------------------------- /lib/connect-sqlite3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Connect - SQLite3 3 | * Copyright(c) 2012 David Feinberg 4 | * MIT Licensed 5 | * forked from https://github.com/tnantoka/connect-sqlite 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | var events = require('events'); 12 | 13 | /** 14 | * @type {Integer} One day in milliseconds. 15 | */ 16 | var oneDay = 86400000; 17 | 18 | /** 19 | * Return the SQLiteStore extending connect's session Store. 20 | * 21 | * @param {object} connect 22 | * @return {Function} 23 | * @api public 24 | */ 25 | module.exports = function(connect) { 26 | /** 27 | * Connect's Store. 28 | */ 29 | var Store = (connect.session) ? connect.session.Store : connect.Store; 30 | 31 | /** 32 | * Remove expired sessions from database. 33 | * @param {Object} store 34 | * @api private 35 | */ 36 | function dbCleanup(store) { 37 | var now = new Date().getTime(); 38 | store.db.run('DELETE FROM ' + store.table + ' WHERE ? > expired', [now]); 39 | } 40 | 41 | /** 42 | * Initialize SQLiteStore with the given options. 43 | * 44 | * @param {Object} options 45 | * @api public 46 | */ 47 | function SQLiteStore(options) { 48 | options = options || {}; 49 | Store.call(this, options); 50 | 51 | this.table = options.table || 'sessions'; 52 | this.db = options.db; 53 | this.client = new events.EventEmitter(); 54 | var self = this; 55 | 56 | this.db.exec((options.concurrentDb ? 'PRAGMA journal_mode = wal; ' : '') + 'CREATE TABLE IF NOT EXISTS ' + this.table + ' (' + 'sid PRIMARY KEY, ' + 'expired, sess)', 57 | function(err) { 58 | if (err) throw err; 59 | self.client.emit('connect'); 60 | 61 | dbCleanup(self); 62 | setInterval(dbCleanup, oneDay, self).unref(); 63 | } 64 | ); 65 | } 66 | 67 | /** 68 | * Inherit from Store. 69 | */ 70 | SQLiteStore.prototype = Object.create(Store.prototype); 71 | SQLiteStore.prototype.constructor = SQLiteStore; 72 | 73 | /** 74 | * Attempt to fetch session by the given sid. 75 | * 76 | * @param {String} sid 77 | * @param {Function} fn 78 | * @api public 79 | */ 80 | SQLiteStore.prototype.get = function(sid, fn) { 81 | var now = new Date().getTime(); 82 | this.db.get('SELECT sess FROM ' + this.table + ' WHERE sid = ? AND ? <= expired', [sid, now], 83 | function(err, row) { 84 | if (err) fn(err); 85 | if (!row) return fn(); 86 | fn(null, JSON.parse(row.sess)); 87 | } 88 | ); 89 | }; 90 | 91 | /** 92 | * Commit the given `sess` object associated with the given `sid`. 93 | * 94 | * @param {String} sid 95 | * @param {Session} sess 96 | * @param {Function} fn 97 | * @api public 98 | */ 99 | SQLiteStore.prototype.set = function(sid, sess, fn) { 100 | try { 101 | var maxAge = sess.cookie.maxAge; 102 | var now = new Date().getTime(); 103 | var expired = maxAge ? now + maxAge : now + oneDay; 104 | sess = JSON.stringify(sess); 105 | 106 | this.db.all('INSERT OR REPLACE INTO ' + this.table + ' VALUES (?, ?, ?)', 107 | [sid, expired, sess], 108 | function(err, rows) { 109 | if (fn) fn.apply(this, arguments); 110 | } 111 | ); 112 | } catch (e) { 113 | if (fn) fn(e); 114 | } 115 | }; 116 | 117 | /** 118 | * Destroy the session associated with the given `sid`. 119 | * 120 | * @param {String} sid 121 | * @api public 122 | */ 123 | SQLiteStore.prototype.destroy = function(sid, fn) { 124 | this.db.run('DELETE FROM ' + this.table + ' WHERE sid = ?', [sid], fn); 125 | }; 126 | 127 | /** 128 | * Fetch all sessions. 129 | * 130 | * @param {Function} fn 131 | * @api public 132 | */ 133 | SQLiteStore.prototype.all = function(fn) { 134 | this.db.all('SELECT * FROM ' + this.table + '', function(err, rows) { 135 | if (err) fn(err); 136 | fn(null, rows.map((row) => JSON.parse(row.sess))); 137 | }); 138 | }; 139 | 140 | /** 141 | * Fetch number of sessions. 142 | * 143 | * @param {Function} fn 144 | * @api public 145 | */ 146 | SQLiteStore.prototype.length = function(fn) { 147 | this.db.all('SELECT COUNT(*) AS count FROM ' + this.table + '', function(err, rows) { 148 | if (err) fn(err); 149 | fn(null, rows[0].count); 150 | }); 151 | }; 152 | 153 | /** 154 | * Clear all sessions. 155 | * 156 | * @param {Function} fn 157 | * @api public 158 | */ 159 | SQLiteStore.prototype.clear = function(fn) { 160 | this.db.exec('DELETE FROM ' + this.table + '', function(err) { 161 | if (err) fn(err); 162 | fn(null, true); 163 | }); 164 | }; 165 | 166 | /** 167 | * Touch the given session object associated with the given session ID. 168 | * 169 | * @param {string} sid 170 | * @param {object} session 171 | * @param {function} fn 172 | * @public 173 | */ 174 | SQLiteStore.prototype.touch = function(sid, session, fn) { 175 | if (session && session.cookie && session.cookie.expires) { 176 | var now = new Date().getTime(); 177 | var cookieExpires = new Date(session.cookie.expires).getTime(); 178 | this.db.run('UPDATE ' + this.table + ' SET expired=? WHERE sid = ? AND ? <= expired', 179 | [cookieExpires, sid, now], 180 | function(err) { 181 | if (fn) { 182 | if (err) fn(err); 183 | fn(null, true); 184 | } 185 | } 186 | ); 187 | } else { 188 | fn(null, true); 189 | } 190 | } 191 | 192 | return SQLiteStore; 193 | }; 194 | -------------------------------------------------------------------------------- /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 | * Inspect the context void of `._runnable`. 438 | * 439 | * @return {String} 440 | * @api private 441 | */ 442 | 443 | Context.prototype.inspect = function(){ 444 | return JSON.stringify(this, function(key, val){ 445 | if ('_runnable' == key) return; 446 | if ('test' == key) return; 447 | return val; 448 | }, 2); 449 | }; 450 | 451 | }); // module: context.js 452 | 453 | require.register("hook.js", function(module, exports, require){ 454 | 455 | /** 456 | * Module dependencies. 457 | */ 458 | 459 | var Runnable = require('./runnable'); 460 | 461 | /** 462 | * Expose `Hook`. 463 | */ 464 | 465 | module.exports = Hook; 466 | 467 | /** 468 | * Initialize a new `Hook` with the given `title` and callback `fn`. 469 | * 470 | * @param {String} title 471 | * @param {Function} fn 472 | * @api private 473 | */ 474 | 475 | function Hook(title, fn) { 476 | Runnable.call(this, title, fn); 477 | this.type = 'hook'; 478 | } 479 | 480 | /** 481 | * Inherit from `Runnable.prototype`. 482 | */ 483 | 484 | Hook.prototype = new Runnable; 485 | Hook.prototype.constructor = Hook; 486 | 487 | 488 | /** 489 | * Get or set the test `err`. 490 | * 491 | * @param {Error} err 492 | * @return {Error} 493 | * @api public 494 | */ 495 | 496 | Hook.prototype.error = function(err){ 497 | if (0 == arguments.length) { 498 | var err = this._error; 499 | this._error = null; 500 | return err; 501 | } 502 | 503 | this._error = err; 504 | }; 505 | 506 | 507 | }); // module: hook.js 508 | 509 | require.register("interfaces/bdd.js", function(module, exports, require){ 510 | 511 | /** 512 | * Module dependencies. 513 | */ 514 | 515 | var Suite = require('../suite') 516 | , Test = require('../test'); 517 | 518 | /** 519 | * BDD-style interface: 520 | * 521 | * describe('Array', function(){ 522 | * describe('#indexOf()', function(){ 523 | * it('should return -1 when not present', function(){ 524 | * 525 | * }); 526 | * 527 | * it('should return the index when present', function(){ 528 | * 529 | * }); 530 | * }); 531 | * }); 532 | * 533 | */ 534 | 535 | module.exports = function(suite){ 536 | var suites = [suite]; 537 | 538 | suite.on('pre-require', function(context){ 539 | 540 | // noop variants 541 | 542 | context.xdescribe = function(){}; 543 | context.xit = function(){}; 544 | 545 | /** 546 | * Execute before running tests. 547 | */ 548 | 549 | context.before = function(fn){ 550 | suites[0].beforeAll(fn); 551 | }; 552 | 553 | /** 554 | * Execute after running tests. 555 | */ 556 | 557 | context.after = function(fn){ 558 | suites[0].afterAll(fn); 559 | }; 560 | 561 | /** 562 | * Execute before each test case. 563 | */ 564 | 565 | context.beforeEach = function(fn){ 566 | suites[0].beforeEach(fn); 567 | }; 568 | 569 | /** 570 | * Execute after each test case. 571 | */ 572 | 573 | context.afterEach = function(fn){ 574 | suites[0].afterEach(fn); 575 | }; 576 | 577 | /** 578 | * Describe a "suite" with the given `title` 579 | * and callback `fn` containing nested suites 580 | * and/or tests. 581 | */ 582 | 583 | context.describe = context.context = function(title, fn){ 584 | var suite = Suite.create(suites[0], title); 585 | suites.unshift(suite); 586 | fn(); 587 | suites.shift(); 588 | }; 589 | 590 | /** 591 | * Describe a specification or test-case 592 | * with the given `title` and callback `fn` 593 | * acting as a thunk. 594 | */ 595 | 596 | context.it = context.specify = function(title, fn){ 597 | suites[0].addTest(new Test(title, fn)); 598 | }; 599 | }); 600 | }; 601 | 602 | }); // module: interfaces/bdd.js 603 | 604 | require.register("interfaces/exports.js", function(module, exports, require){ 605 | 606 | /** 607 | * Module dependencies. 608 | */ 609 | 610 | var Suite = require('../suite') 611 | , Test = require('../test'); 612 | 613 | /** 614 | * TDD-style interface: 615 | * 616 | * exports.Array = { 617 | * '#indexOf()': { 618 | * 'should return -1 when the value is not present': function(){ 619 | * 620 | * }, 621 | * 622 | * 'should return the correct index when the value is present': function(){ 623 | * 624 | * } 625 | * } 626 | * }; 627 | * 628 | */ 629 | 630 | module.exports = function(suite){ 631 | var suites = [suite]; 632 | 633 | suite.on('require', visit); 634 | 635 | function visit(obj) { 636 | var suite; 637 | for (var key in obj) { 638 | if ('function' == typeof obj[key]) { 639 | var fn = obj[key]; 640 | switch (key) { 641 | case 'before': 642 | suites[0].beforeAll(fn); 643 | break; 644 | case 'after': 645 | suites[0].afterAll(fn); 646 | break; 647 | case 'beforeEach': 648 | suites[0].beforeEach(fn); 649 | break; 650 | case 'afterEach': 651 | suites[0].afterEach(fn); 652 | break; 653 | default: 654 | suites[0].addTest(new Test(key, fn)); 655 | } 656 | } else { 657 | var suite = Suite.create(suites[0], key); 658 | suites.unshift(suite); 659 | visit(obj[key]); 660 | suites.shift(); 661 | } 662 | } 663 | } 664 | }; 665 | }); // module: interfaces/exports.js 666 | 667 | require.register("interfaces/index.js", function(module, exports, require){ 668 | 669 | exports.bdd = require('./bdd'); 670 | exports.tdd = require('./tdd'); 671 | exports.qunit = require('./qunit'); 672 | exports.exports = require('./exports'); 673 | 674 | }); // module: interfaces/index.js 675 | 676 | require.register("interfaces/qunit.js", function(module, exports, require){ 677 | 678 | /** 679 | * Module dependencies. 680 | */ 681 | 682 | var Suite = require('../suite') 683 | , Test = require('../test'); 684 | 685 | /** 686 | * QUnit-style interface: 687 | * 688 | * suite('Array'); 689 | * 690 | * test('#length', function(){ 691 | * var arr = [1,2,3]; 692 | * ok(arr.length == 3); 693 | * }); 694 | * 695 | * test('#indexOf()', function(){ 696 | * var arr = [1,2,3]; 697 | * ok(arr.indexOf(1) == 0); 698 | * ok(arr.indexOf(2) == 1); 699 | * ok(arr.indexOf(3) == 2); 700 | * }); 701 | * 702 | * suite('String'); 703 | * 704 | * test('#length', function(){ 705 | * ok('foo'.length == 3); 706 | * }); 707 | * 708 | */ 709 | 710 | module.exports = function(suite){ 711 | var suites = [suite]; 712 | 713 | suite.on('pre-require', function(context){ 714 | 715 | /** 716 | * Execute before running tests. 717 | */ 718 | 719 | context.before = function(fn){ 720 | suites[0].beforeAll(fn); 721 | }; 722 | 723 | /** 724 | * Execute after running tests. 725 | */ 726 | 727 | context.after = function(fn){ 728 | suites[0].afterAll(fn); 729 | }; 730 | 731 | /** 732 | * Execute before each test case. 733 | */ 734 | 735 | context.beforeEach = function(fn){ 736 | suites[0].beforeEach(fn); 737 | }; 738 | 739 | /** 740 | * Execute after each test case. 741 | */ 742 | 743 | context.afterEach = function(fn){ 744 | suites[0].afterEach(fn); 745 | }; 746 | 747 | /** 748 | * Describe a "suite" with the given `title`. 749 | */ 750 | 751 | context.suite = function(title){ 752 | if (suites.length > 1) suites.shift(); 753 | var suite = Suite.create(suites[0], title); 754 | suites.unshift(suite); 755 | }; 756 | 757 | /** 758 | * Describe a specification or test-case 759 | * with the given `title` and callback `fn` 760 | * acting as a thunk. 761 | */ 762 | 763 | context.test = function(title, fn){ 764 | suites[0].addTest(new Test(title, fn)); 765 | }; 766 | }); 767 | }; 768 | 769 | }); // module: interfaces/qunit.js 770 | 771 | require.register("interfaces/tdd.js", function(module, exports, require){ 772 | 773 | /** 774 | * Module dependencies. 775 | */ 776 | 777 | var Suite = require('../suite') 778 | , Test = require('../test'); 779 | 780 | /** 781 | * TDD-style interface: 782 | * 783 | * suite('Array', function(){ 784 | * suite('#indexOf()', function(){ 785 | * suiteSetup(function(){ 786 | * 787 | * }); 788 | * 789 | * test('should return -1 when not present', function(){ 790 | * 791 | * }); 792 | * 793 | * test('should return the index when present', function(){ 794 | * 795 | * }); 796 | * 797 | * suiteTeardown(function(){ 798 | * 799 | * }); 800 | * }); 801 | * }); 802 | * 803 | */ 804 | 805 | module.exports = function(suite){ 806 | var suites = [suite]; 807 | 808 | suite.on('pre-require', function(context){ 809 | 810 | /** 811 | * Execute before each test case. 812 | */ 813 | 814 | context.setup = function(fn){ 815 | suites[0].beforeEach(fn); 816 | }; 817 | 818 | /** 819 | * Execute after each test case. 820 | */ 821 | 822 | context.teardown = function(fn){ 823 | suites[0].afterEach(fn); 824 | }; 825 | 826 | /** 827 | * Execute before the suite. 828 | */ 829 | 830 | context.suiteSetup = function(fn){ 831 | suites[0].beforeAll(fn); 832 | }; 833 | 834 | /** 835 | * Execute after the suite. 836 | */ 837 | 838 | context.suiteTeardown = function(fn){ 839 | suites[0].afterAll(fn); 840 | }; 841 | 842 | /** 843 | * Describe a "suite" with the given `title` 844 | * and callback `fn` containing nested suites 845 | * and/or tests. 846 | */ 847 | 848 | context.suite = function(title, fn){ 849 | var suite = Suite.create(suites[0], title); 850 | suites.unshift(suite); 851 | fn(); 852 | suites.shift(); 853 | }; 854 | 855 | /** 856 | * Describe a specification or test-case 857 | * with the given `title` and callback `fn` 858 | * acting as a thunk. 859 | */ 860 | 861 | context.test = function(title, fn){ 862 | suites[0].addTest(new Test(title, fn)); 863 | }; 864 | }); 865 | }; 866 | 867 | }); // module: interfaces/tdd.js 868 | 869 | require.register("mocha.js", function(module, exports, require){ 870 | /*! 871 | * mocha 872 | * Copyright(c) 2011 TJ Holowaychuk 873 | * MIT Licensed 874 | */ 875 | 876 | /** 877 | * Module dependencies. 878 | */ 879 | 880 | var path = require('browser/path'); 881 | 882 | /** 883 | * Expose `Mocha`. 884 | */ 885 | 886 | exports = module.exports = Mocha; 887 | 888 | /** 889 | * Expose internals. 890 | */ 891 | 892 | exports.utils = require('./utils'); 893 | exports.interfaces = require('./interfaces'); 894 | exports.reporters = require('./reporters'); 895 | exports.Runnable = require('./runnable'); 896 | exports.Context = require('./context'); 897 | exports.Runner = require('./runner'); 898 | exports.Suite = require('./suite'); 899 | exports.Hook = require('./hook'); 900 | exports.Test = require('./test'); 901 | 902 | /** 903 | * Return image `name` path. 904 | * 905 | * @param {String} name 906 | * @return {String} 907 | * @api private 908 | */ 909 | 910 | function image(name) { 911 | return __dirname + '/../images/' + name + '.png'; 912 | } 913 | 914 | /** 915 | * Setup mocha with `options`. 916 | * 917 | * Options: 918 | * 919 | * - `ui` name "bdd", "tdd", "exports" etc 920 | * - `reporter` reporter instance, defaults to `mocha.reporters.Dot` 921 | * - `globals` array of accepted globals 922 | * - `timeout` timeout in milliseconds 923 | * - `ignoreLeaks` ignore global leaks 924 | * - `grep` string or regexp to filter tests with 925 | * 926 | * @param {Object} options 927 | * @api public 928 | */ 929 | 930 | function Mocha(options) { 931 | options = options || {}; 932 | this.files = []; 933 | this.options = options; 934 | this.grep(options.grep); 935 | this.suite = new exports.Suite('', new exports.Context); 936 | this.ui(options.ui); 937 | this.reporter(options.reporter); 938 | if (options.timeout) this.suite.timeout(options.timeout); 939 | } 940 | 941 | /** 942 | * Add test `file`. 943 | * 944 | * @param {String} file 945 | * @api public 946 | */ 947 | 948 | Mocha.prototype.addFile = function(file){ 949 | this.files.push(file); 950 | return this; 951 | }; 952 | 953 | /** 954 | * Set reporter to `name`, defaults to "dot". 955 | * 956 | * @param {String} name 957 | * @api public 958 | */ 959 | 960 | Mocha.prototype.reporter = function(name){ 961 | name = name || 'dot'; 962 | this._reporter = require('./reporters/' + name); 963 | if (!this._reporter) throw new Error('invalid reporter "' + name + '"'); 964 | return this; 965 | }; 966 | 967 | /** 968 | * Set test UI `name`, defaults to "bdd". 969 | * 970 | * @param {String} bdd 971 | * @api public 972 | */ 973 | 974 | Mocha.prototype.ui = function(name){ 975 | name = name || 'bdd'; 976 | this._ui = exports.interfaces[name]; 977 | if (!this._ui) throw new Error('invalid interface "' + name + '"'); 978 | this._ui = this._ui(this.suite); 979 | return this; 980 | }; 981 | 982 | /** 983 | * Load registered files. 984 | * 985 | * @api private 986 | */ 987 | 988 | Mocha.prototype.loadFiles = function(){ 989 | var suite = this.suite; 990 | this.files.forEach(function(file){ 991 | file = path.resolve(file); 992 | suite.emit('pre-require', global, file); 993 | suite.emit('require', require(file), file); 994 | suite.emit('post-require', global, file); 995 | }); 996 | }; 997 | 998 | /** 999 | * Enable growl support. 1000 | * 1001 | * @api private 1002 | */ 1003 | 1004 | Mocha.prototype.growl = function(runner, reporter) { 1005 | var notify = require('growl'); 1006 | 1007 | runner.on('end', function(){ 1008 | var stats = reporter.stats; 1009 | if (stats.failures) { 1010 | var msg = stats.failures + ' of ' + runner.total + ' tests failed'; 1011 | notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); 1012 | } else { 1013 | notify(stats.passes + ' tests passed in ' + stats.duration + 'ms', { 1014 | name: 'mocha' 1015 | , title: 'Passed' 1016 | , image: image('ok') 1017 | }); 1018 | } 1019 | }); 1020 | }; 1021 | 1022 | /** 1023 | * Add regexp to grep for to the options object 1024 | * 1025 | * @param {RegExp} or {String} re 1026 | * @return {Mocha} 1027 | * @api public 1028 | */ 1029 | 1030 | Mocha.prototype.grep = function(re){ 1031 | this.options.grep = 'string' == typeof re 1032 | ? new RegExp(re) 1033 | : re; 1034 | return this; 1035 | }; 1036 | 1037 | /** 1038 | * Run tests and invoke `fn()` when complete. 1039 | * 1040 | * @param {Function} fn 1041 | * @return {Runner} 1042 | * @api public 1043 | */ 1044 | 1045 | Mocha.prototype.run = function(fn){ 1046 | this.loadFiles(); 1047 | var suite = this.suite; 1048 | var options = this.options; 1049 | var runner = new exports.Runner(suite); 1050 | var reporter = new this._reporter(runner); 1051 | runner.ignoreLeaks = options.ignoreLeaks; 1052 | if (options.grep) runner.grep(options.grep); 1053 | if (options.globals) runner.globals(options.globals); 1054 | if (options.growl) this.growl(runner, reporter); 1055 | return runner.run(fn); 1056 | }; 1057 | 1058 | }); // module: mocha.js 1059 | 1060 | require.register("reporters/base.js", function(module, exports, require){ 1061 | 1062 | /** 1063 | * Module dependencies. 1064 | */ 1065 | 1066 | var tty = require('browser/tty') 1067 | , diff = require('browser/diff'); 1068 | 1069 | /** 1070 | * Save timer references to avoid Sinon interfering (see GH-237). 1071 | */ 1072 | 1073 | var Date = global.Date 1074 | , setTimeout = global.setTimeout 1075 | , setInterval = global.setInterval 1076 | , clearTimeout = global.clearTimeout 1077 | , clearInterval = global.clearInterval; 1078 | 1079 | /** 1080 | * Check if both stdio streams are associated with a tty. 1081 | */ 1082 | 1083 | var isatty = tty.isatty(1) && tty.isatty(2); 1084 | 1085 | /** 1086 | * Expose `Base`. 1087 | */ 1088 | 1089 | exports = module.exports = Base; 1090 | 1091 | /** 1092 | * Enable coloring by default. 1093 | */ 1094 | 1095 | exports.useColors = isatty; 1096 | 1097 | /** 1098 | * Default color map. 1099 | */ 1100 | 1101 | exports.colors = { 1102 | 'pass': 90 1103 | , 'fail': 31 1104 | , 'bright pass': 92 1105 | , 'bright fail': 91 1106 | , 'bright yellow': 93 1107 | , 'pending': 36 1108 | , 'suite': 0 1109 | , 'error title': 0 1110 | , 'error message': 31 1111 | , 'error stack': 90 1112 | , 'checkmark': 32 1113 | , 'fast': 90 1114 | , 'medium': 33 1115 | , 'slow': 31 1116 | , 'green': 32 1117 | , 'light': 90 1118 | , 'diff gutter': 90 1119 | , 'diff added': 42 1120 | , 'diff removed': 41 1121 | }; 1122 | 1123 | /** 1124 | * Color `str` with the given `type`, 1125 | * allowing colors to be disabled, 1126 | * as well as user-defined color 1127 | * schemes. 1128 | * 1129 | * @param {String} type 1130 | * @param {String} str 1131 | * @return {String} 1132 | * @api private 1133 | */ 1134 | 1135 | var color = exports.color = function(type, str) { 1136 | if (!exports.useColors) return str; 1137 | return '\033[' + exports.colors[type] + 'm' + str + '\033[0m'; 1138 | }; 1139 | 1140 | /** 1141 | * Expose term window size, with some 1142 | * defaults for when stderr is not a tty. 1143 | */ 1144 | 1145 | exports.window = { 1146 | width: isatty 1147 | ? process.stdout.getWindowSize 1148 | ? process.stdout.getWindowSize(1)[0] 1149 | : tty.getWindowSize()[1] 1150 | : 75 1151 | }; 1152 | 1153 | /** 1154 | * Expose some basic cursor interactions 1155 | * that are common among reporters. 1156 | */ 1157 | 1158 | exports.cursor = { 1159 | hide: function(){ 1160 | process.stdout.write('\033[?25l'); 1161 | }, 1162 | 1163 | show: function(){ 1164 | process.stdout.write('\033[?25h'); 1165 | }, 1166 | 1167 | deleteLine: function(){ 1168 | process.stdout.write('\033[2K'); 1169 | }, 1170 | 1171 | beginningOfLine: function(){ 1172 | process.stdout.write('\033[0G'); 1173 | }, 1174 | 1175 | CR: function(){ 1176 | exports.cursor.deleteLine(); 1177 | exports.cursor.beginningOfLine(); 1178 | } 1179 | }; 1180 | 1181 | /** 1182 | * A test is considered slow if it 1183 | * exceeds the following value in milliseconds. 1184 | */ 1185 | 1186 | exports.slow = 75; 1187 | 1188 | /** 1189 | * Outut the given `failures` as a list. 1190 | * 1191 | * @param {Array} failures 1192 | * @api public 1193 | */ 1194 | 1195 | exports.list = function(failures){ 1196 | console.error(); 1197 | failures.forEach(function(test, i){ 1198 | // format 1199 | var fmt = color('error title', ' %s) %s:\n') 1200 | + color('error message', ' %s') 1201 | + color('error stack', '\n%s\n'); 1202 | 1203 | // msg 1204 | var err = test.err 1205 | , message = err.message || '' 1206 | , stack = err.stack || message 1207 | , index = stack.indexOf(message) + message.length 1208 | , msg = stack.slice(0, index) 1209 | , actual = err.actual 1210 | , expected = err.expected; 1211 | 1212 | // actual / expected diff 1213 | if ('string' == typeof actual && 'string' == typeof expected) { 1214 | var len = Math.max(actual.length, expected.length); 1215 | 1216 | if (len < 20) msg = errorDiff(err, 'Chars'); 1217 | else msg = errorDiff(err, 'Words'); 1218 | 1219 | // linenos 1220 | var lines = msg.split('\n'); 1221 | if (lines.length > 4) { 1222 | var width = String(lines.length).length; 1223 | msg = lines.map(function(str, i){ 1224 | return pad(++i, width) + ' |' + ' ' + str; 1225 | }).join('\n'); 1226 | } 1227 | 1228 | // legend 1229 | msg = '\n' 1230 | + color('diff removed', 'actual') 1231 | + ' ' 1232 | + color('diff added', 'expected') 1233 | + '\n\n' 1234 | + msg 1235 | + '\n'; 1236 | 1237 | // indent 1238 | msg = msg.replace(/^/gm, ' '); 1239 | 1240 | fmt = color('error title', ' %s) %s:\n%s') 1241 | + color('error stack', '\n%s\n'); 1242 | } 1243 | 1244 | // indent stack trace without msg 1245 | stack = stack.slice(index ? index + 1 : index) 1246 | .replace(/^/gm, ' '); 1247 | 1248 | console.error(fmt, (i + 1), test.fullTitle(), msg, stack); 1249 | }); 1250 | }; 1251 | 1252 | /** 1253 | * Initialize a new `Base` reporter. 1254 | * 1255 | * All other reporters generally 1256 | * inherit from this reporter, providing 1257 | * stats such as test duration, number 1258 | * of tests passed / failed etc. 1259 | * 1260 | * @param {Runner} runner 1261 | * @api public 1262 | */ 1263 | 1264 | function Base(runner) { 1265 | var self = this 1266 | , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 } 1267 | , failures = this.failures = []; 1268 | 1269 | if (!runner) return; 1270 | this.runner = runner; 1271 | 1272 | runner.on('start', function(){ 1273 | stats.start = new Date; 1274 | }); 1275 | 1276 | runner.on('suite', function(suite){ 1277 | stats.suites = stats.suites || 0; 1278 | suite.root || stats.suites++; 1279 | }); 1280 | 1281 | runner.on('test end', function(test){ 1282 | stats.tests = stats.tests || 0; 1283 | stats.tests++; 1284 | }); 1285 | 1286 | runner.on('pass', function(test){ 1287 | stats.passes = stats.passes || 0; 1288 | 1289 | var medium = exports.slow / 2; 1290 | test.speed = test.duration > exports.slow 1291 | ? 'slow' 1292 | : test.duration > medium 1293 | ? 'medium' 1294 | : 'fast'; 1295 | 1296 | stats.passes++; 1297 | }); 1298 | 1299 | runner.on('fail', function(test, err){ 1300 | stats.failures = stats.failures || 0; 1301 | stats.failures++; 1302 | test.err = err; 1303 | failures.push(test); 1304 | }); 1305 | 1306 | runner.on('end', function(){ 1307 | stats.end = new Date; 1308 | stats.duration = new Date - stats.start; 1309 | }); 1310 | 1311 | runner.on('pending', function(){ 1312 | stats.pending++; 1313 | }); 1314 | } 1315 | 1316 | /** 1317 | * Output common epilogue used by many of 1318 | * the bundled reporters. 1319 | * 1320 | * @api public 1321 | */ 1322 | 1323 | Base.prototype.epilogue = function(){ 1324 | var stats = this.stats 1325 | , fmt 1326 | , tests; 1327 | 1328 | console.log(); 1329 | 1330 | function pluralize(n) { 1331 | return 1 == n ? 'test' : 'tests'; 1332 | } 1333 | 1334 | // failure 1335 | if (stats.failures) { 1336 | fmt = color('bright fail', ' ✖') 1337 | + color('fail', ' %d of %d %s failed') 1338 | + color('light', ':') 1339 | 1340 | console.error(fmt, 1341 | stats.failures, 1342 | this.runner.total, 1343 | pluralize(this.runner.total)); 1344 | 1345 | Base.list(this.failures); 1346 | console.error(); 1347 | return; 1348 | } 1349 | 1350 | // pass 1351 | fmt = color('bright pass', ' ✔') 1352 | + color('green', ' %d %s complete') 1353 | + color('light', ' (%dms)'); 1354 | 1355 | console.log(fmt, 1356 | stats.tests || 0, 1357 | pluralize(stats.tests), 1358 | stats.duration); 1359 | 1360 | // pending 1361 | if (stats.pending) { 1362 | fmt = color('pending', ' •') 1363 | + color('pending', ' %d %s pending'); 1364 | 1365 | console.log(fmt, stats.pending, pluralize(stats.pending)); 1366 | } 1367 | 1368 | console.log(); 1369 | }; 1370 | 1371 | /** 1372 | * Pad the given `str` to `len`. 1373 | * 1374 | * @param {String} str 1375 | * @param {String} len 1376 | * @return {String} 1377 | * @api private 1378 | */ 1379 | 1380 | function pad(str, len) { 1381 | str = String(str); 1382 | return Array(len - str.length + 1).join(' ') + str; 1383 | } 1384 | 1385 | /** 1386 | * Return a character diff for `err`. 1387 | * 1388 | * @param {Error} err 1389 | * @return {String} 1390 | * @api private 1391 | */ 1392 | 1393 | function errorDiff(err, type) { 1394 | return diff['diff' + type](err.actual, err.expected).map(function(str){ 1395 | if (/^(\n+)$/.test(str.value)) str.value = Array(++RegExp.$1.length).join(''); 1396 | if (str.added) return colorLines('diff added', str.value); 1397 | if (str.removed) return colorLines('diff removed', str.value); 1398 | return str.value; 1399 | }).join(''); 1400 | } 1401 | 1402 | /** 1403 | * Color lines for `str`, using the color `name`. 1404 | * 1405 | * @param {String} name 1406 | * @param {String} str 1407 | * @return {String} 1408 | * @api private 1409 | */ 1410 | 1411 | function colorLines(name, str) { 1412 | return str.split('\n').map(function(str){ 1413 | return color(name, str); 1414 | }).join('\n'); 1415 | } 1416 | 1417 | }); // module: reporters/base.js 1418 | 1419 | require.register("reporters/doc.js", function(module, exports, require){ 1420 | 1421 | /** 1422 | * Module dependencies. 1423 | */ 1424 | 1425 | var Base = require('./base') 1426 | , utils = require('../utils'); 1427 | 1428 | /** 1429 | * Expose `Doc`. 1430 | */ 1431 | 1432 | exports = module.exports = Doc; 1433 | 1434 | /** 1435 | * Initialize a new `Doc` reporter. 1436 | * 1437 | * @param {Runner} runner 1438 | * @api public 1439 | */ 1440 | 1441 | function Doc(runner) { 1442 | Base.call(this, runner); 1443 | 1444 | var self = this 1445 | , stats = this.stats 1446 | , total = runner.total 1447 | , indents = 2; 1448 | 1449 | function indent() { 1450 | return Array(indents).join(' '); 1451 | } 1452 | 1453 | runner.on('suite', function(suite){ 1454 | if (suite.root) return; 1455 | ++indents; 1456 | console.log('%s
', indent()); 1457 | ++indents; 1458 | console.log('%s

%s

', indent(), suite.title); 1459 | console.log('%s
', indent()); 1460 | }); 1461 | 1462 | runner.on('suite end', function(suite){ 1463 | if (suite.root) return; 1464 | console.log('%s
', indent()); 1465 | --indents; 1466 | console.log('%s
', indent()); 1467 | --indents; 1468 | }); 1469 | 1470 | runner.on('pass', function(test){ 1471 | console.log('%s
%s
', indent(), test.title); 1472 | var code = utils.escape(utils.clean(test.fn.toString())); 1473 | console.log('%s
%s
', indent(), code); 1474 | }); 1475 | } 1476 | 1477 | }); // module: reporters/doc.js 1478 | 1479 | require.register("reporters/dot.js", function(module, exports, require){ 1480 | 1481 | /** 1482 | * Module dependencies. 1483 | */ 1484 | 1485 | var Base = require('./base') 1486 | , color = Base.color; 1487 | 1488 | /** 1489 | * Expose `Dot`. 1490 | */ 1491 | 1492 | exports = module.exports = Dot; 1493 | 1494 | /** 1495 | * Initialize a new `Dot` matrix test reporter. 1496 | * 1497 | * @param {Runner} runner 1498 | * @api public 1499 | */ 1500 | 1501 | function Dot(runner) { 1502 | Base.call(this, runner); 1503 | 1504 | var self = this 1505 | , stats = this.stats 1506 | , width = Base.window.width * .75 | 0 1507 | , n = 0; 1508 | 1509 | runner.on('start', function(){ 1510 | process.stdout.write('\n '); 1511 | }); 1512 | 1513 | runner.on('pending', function(test){ 1514 | process.stdout.write(color('pending', '.')); 1515 | }); 1516 | 1517 | runner.on('pass', function(test){ 1518 | if (++n % width == 0) process.stdout.write('\n '); 1519 | if ('slow' == test.speed) { 1520 | process.stdout.write(color('bright yellow', '.')); 1521 | } else { 1522 | process.stdout.write(color(test.speed, '.')); 1523 | } 1524 | }); 1525 | 1526 | runner.on('fail', function(test, err){ 1527 | if (++n % width == 0) process.stdout.write('\n '); 1528 | process.stdout.write(color('fail', '.')); 1529 | }); 1530 | 1531 | runner.on('end', function(){ 1532 | console.log(); 1533 | self.epilogue(); 1534 | }); 1535 | } 1536 | 1537 | /** 1538 | * Inherit from `Base.prototype`. 1539 | */ 1540 | 1541 | Dot.prototype = new Base; 1542 | Dot.prototype.constructor = Dot; 1543 | 1544 | }); // module: reporters/dot.js 1545 | 1546 | require.register("reporters/html-cov.js", function(module, exports, require){ 1547 | 1548 | /** 1549 | * Module dependencies. 1550 | */ 1551 | 1552 | var JSONCov = require('./json-cov') 1553 | , fs = require('browser/fs'); 1554 | 1555 | /** 1556 | * Expose `HTMLCov`. 1557 | */ 1558 | 1559 | exports = module.exports = HTMLCov; 1560 | 1561 | /** 1562 | * Initialize a new `JsCoverage` reporter. 1563 | * 1564 | * @param {Runner} runner 1565 | * @api public 1566 | */ 1567 | 1568 | function HTMLCov(runner) { 1569 | var jade = require('jade') 1570 | , file = __dirname + '/templates/coverage.jade' 1571 | , str = fs.readFileSync(file, 'utf8') 1572 | , fn = jade.compile(str, { filename: file }) 1573 | , self = this; 1574 | 1575 | JSONCov.call(this, runner, false); 1576 | 1577 | runner.on('end', function(){ 1578 | process.stdout.write(fn({ 1579 | cov: self.cov 1580 | , coverageClass: coverageClass 1581 | })); 1582 | }); 1583 | } 1584 | 1585 | /** 1586 | * Return coverage class for `n`. 1587 | * 1588 | * @return {String} 1589 | * @api private 1590 | */ 1591 | 1592 | function coverageClass(n) { 1593 | if (n >= 75) return 'high'; 1594 | if (n >= 50) return 'medium'; 1595 | if (n >= 25) return 'low'; 1596 | return 'terrible'; 1597 | } 1598 | }); // module: reporters/html-cov.js 1599 | 1600 | require.register("reporters/html.js", function(module, exports, require){ 1601 | 1602 | /** 1603 | * Module dependencies. 1604 | */ 1605 | 1606 | var Base = require('./base') 1607 | , utils = require('../utils') 1608 | , Progress = require('../browser/progress') 1609 | , escape = utils.escape; 1610 | 1611 | /** 1612 | * Save timer references to avoid Sinon interfering (see GH-237). 1613 | */ 1614 | 1615 | var Date = global.Date 1616 | , setTimeout = global.setTimeout 1617 | , setInterval = global.setInterval 1618 | , clearTimeout = global.clearTimeout 1619 | , clearInterval = global.clearInterval; 1620 | 1621 | /** 1622 | * Expose `Doc`. 1623 | */ 1624 | 1625 | exports = module.exports = HTML; 1626 | 1627 | /** 1628 | * Stats template. 1629 | */ 1630 | 1631 | var statsTemplate = ''; 1637 | 1638 | /** 1639 | * Initialize a new `Doc` reporter. 1640 | * 1641 | * @param {Runner} runner 1642 | * @api public 1643 | */ 1644 | 1645 | function HTML(runner) { 1646 | Base.call(this, runner); 1647 | 1648 | var self = this 1649 | , stats = this.stats 1650 | , total = runner.total 1651 | , root = document.getElementById('mocha') 1652 | , stat = fragment(statsTemplate) 1653 | , items = stat.getElementsByTagName('li') 1654 | , passes = items[1].getElementsByTagName('em')[0] 1655 | , failures = items[2].getElementsByTagName('em')[0] 1656 | , duration = items[3].getElementsByTagName('em')[0] 1657 | , canvas = stat.getElementsByTagName('canvas')[0] 1658 | , report = fragment('') 1659 | , stack = [report] 1660 | , progress 1661 | , ctx 1662 | 1663 | if (canvas.getContext) { 1664 | ctx = canvas.getContext('2d'); 1665 | progress = new Progress; 1666 | } 1667 | 1668 | if (!root) return error('#mocha div missing, add it to your document'); 1669 | 1670 | root.appendChild(stat); 1671 | root.appendChild(report); 1672 | 1673 | if (progress) progress.size(40); 1674 | 1675 | runner.on('suite', function(suite){ 1676 | if (suite.root) return; 1677 | 1678 | // suite 1679 | var url = location.protocol + '//' + location.host + location.pathname + '?grep=^' + utils.escapeRegexp(suite.fullTitle()); 1680 | var el = fragment('
  • %s

  • ', url, escape(suite.title)); 1681 | 1682 | // container 1683 | stack[0].appendChild(el); 1684 | stack.unshift(document.createElement('ul')); 1685 | el.appendChild(stack[0]); 1686 | }); 1687 | 1688 | runner.on('suite end', function(suite){ 1689 | if (suite.root) return; 1690 | stack.shift(); 1691 | }); 1692 | 1693 | runner.on('fail', function(test, err){ 1694 | if ('hook' == test.type || err.uncaught) runner.emit('test end', test); 1695 | }); 1696 | 1697 | runner.on('test end', function(test){ 1698 | window.scrollTo(0, document.body.scrollHeight); 1699 | 1700 | // TODO: add to stats 1701 | var percent = stats.tests / total * 100 | 0; 1702 | if (progress) progress.update(percent).draw(ctx); 1703 | 1704 | // update stats 1705 | var ms = new Date - stats.start; 1706 | text(passes, stats.passes); 1707 | text(failures, stats.failures); 1708 | text(duration, (ms / 1000).toFixed(2)); 1709 | 1710 | // test 1711 | if ('passed' == test.state) { 1712 | var el = fragment('
  • %e%ems

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

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

  • ', test.title); 1717 | var str = test.err.stack || test.err.toString(); 1718 | 1719 | // FF / Opera do not add the message 1720 | if (!~str.indexOf(test.err.message)) { 1721 | str = test.err.message + '\n' + str; 1722 | } 1723 | 1724 | // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we 1725 | // check for the result of the stringifying. 1726 | if ('[object Error]' == str) str = test.err.message; 1727 | 1728 | // Safari doesn't give you a stack. Let's at least provide a source line. 1729 | if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) { 1730 | str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")"; 1731 | } 1732 | 1733 | el.appendChild(fragment('
    %e
    ', str)); 1734 | } 1735 | 1736 | // toggle code 1737 | var h2 = el.getElementsByTagName('h2')[0]; 1738 | 1739 | on(h2, 'click', function(){ 1740 | pre.style.display = 'none' == pre.style.display 1741 | ? 'block' 1742 | : 'none'; 1743 | }); 1744 | 1745 | // code 1746 | // TODO: defer 1747 | if (!test.pending) { 1748 | var pre = fragment('
    %e
    ', utils.clean(test.fn.toString())); 1749 | el.appendChild(pre); 1750 | pre.style.display = 'none'; 1751 | } 1752 | 1753 | stack[0].appendChild(el); 1754 | }); 1755 | } 1756 | 1757 | /** 1758 | * Display error `msg`. 1759 | */ 1760 | 1761 | function error(msg) { 1762 | document.body.appendChild(fragment('
    %s
    ', msg)); 1763 | } 1764 | 1765 | /** 1766 | * Return a DOM fragment from `html`. 1767 | */ 1768 | 1769 | function fragment(html) { 1770 | var args = arguments 1771 | , div = document.createElement('div') 1772 | , i = 1; 1773 | 1774 | div.innerHTML = html.replace(/%([se])/g, function(_, type){ 1775 | switch (type) { 1776 | case 's': return String(args[i++]); 1777 | case 'e': return escape(args[i++]); 1778 | } 1779 | }); 1780 | 1781 | return div.firstChild; 1782 | } 1783 | 1784 | /** 1785 | * Set `el` text to `str`. 1786 | */ 1787 | 1788 | function text(el, str) { 1789 | if (el.textContent) { 1790 | el.textContent = str; 1791 | } else { 1792 | el.innerText = str; 1793 | } 1794 | } 1795 | 1796 | /** 1797 | * Listen on `event` with callback `fn`. 1798 | */ 1799 | 1800 | function on(el, event, fn) { 1801 | if (el.addEventListener) { 1802 | el.addEventListener(event, fn, false); 1803 | } else { 1804 | el.attachEvent('on' + event, fn); 1805 | } 1806 | } 1807 | }); // module: reporters/html.js 1808 | 1809 | require.register("reporters/index.js", function(module, exports, require){ 1810 | 1811 | exports.Base = require('./base'); 1812 | exports.Dot = require('./dot'); 1813 | exports.Doc = require('./doc'); 1814 | exports.TAP = require('./tap'); 1815 | exports.JSON = require('./json'); 1816 | exports.HTML = require('./html'); 1817 | exports.List = require('./list'); 1818 | exports.Min = require('./min'); 1819 | exports.Spec = require('./spec'); 1820 | exports.Nyan = require('./nyan'); 1821 | exports.XUnit = require('./xunit'); 1822 | exports.Progress = require('./progress'); 1823 | exports.Landing = require('./landing'); 1824 | exports.JSONCov = require('./json-cov'); 1825 | exports.HTMLCov = require('./html-cov'); 1826 | exports.JSONStream = require('./json-stream'); 1827 | exports.Teamcity = require('./teamcity'); 1828 | 1829 | }); // module: reporters/index.js 1830 | 1831 | require.register("reporters/json-cov.js", function(module, exports, require){ 1832 | 1833 | /** 1834 | * Module dependencies. 1835 | */ 1836 | 1837 | var Base = require('./base'); 1838 | 1839 | /** 1840 | * Expose `JSONCov`. 1841 | */ 1842 | 1843 | exports = module.exports = JSONCov; 1844 | 1845 | /** 1846 | * Initialize a new `JsCoverage` reporter. 1847 | * 1848 | * @param {Runner} runner 1849 | * @param {Boolean} output 1850 | * @api public 1851 | */ 1852 | 1853 | function JSONCov(runner, output) { 1854 | var self = this 1855 | , output = 1 == arguments.length ? true : output; 1856 | 1857 | Base.call(this, runner); 1858 | 1859 | var tests = [] 1860 | , failures = [] 1861 | , passes = []; 1862 | 1863 | runner.on('test end', function(test){ 1864 | tests.push(test); 1865 | }); 1866 | 1867 | runner.on('pass', function(test){ 1868 | passes.push(test); 1869 | }); 1870 | 1871 | runner.on('fail', function(test){ 1872 | failures.push(test); 1873 | }); 1874 | 1875 | runner.on('end', function(){ 1876 | var cov = global._$jscoverage || {}; 1877 | var result = self.cov = map(cov); 1878 | result.stats = self.stats; 1879 | result.tests = tests.map(clean); 1880 | result.failures = failures.map(clean); 1881 | result.passes = passes.map(clean); 1882 | if (!output) return; 1883 | process.stdout.write(JSON.stringify(result, null, 2 )); 1884 | }); 1885 | } 1886 | 1887 | /** 1888 | * Map jscoverage data to a JSON structure 1889 | * suitable for reporting. 1890 | * 1891 | * @param {Object} cov 1892 | * @return {Object} 1893 | * @api private 1894 | */ 1895 | 1896 | function map(cov) { 1897 | var ret = { 1898 | instrumentation: 'node-jscoverage' 1899 | , sloc: 0 1900 | , hits: 0 1901 | , misses: 0 1902 | , coverage: 0 1903 | , files: [] 1904 | }; 1905 | 1906 | for (var filename in cov) { 1907 | var data = coverage(filename, cov[filename]); 1908 | ret.files.push(data); 1909 | ret.hits += data.hits; 1910 | ret.misses += data.misses; 1911 | ret.sloc += data.sloc; 1912 | } 1913 | 1914 | if (ret.sloc > 0) { 1915 | ret.coverage = (ret.hits / ret.sloc) * 100; 1916 | } 1917 | 1918 | return ret; 1919 | }; 1920 | 1921 | /** 1922 | * Map jscoverage data for a single source file 1923 | * to a JSON structure suitable for reporting. 1924 | * 1925 | * @param {String} filename name of the source file 1926 | * @param {Object} data jscoverage coverage data 1927 | * @return {Object} 1928 | * @api private 1929 | */ 1930 | 1931 | function coverage(filename, data) { 1932 | var ret = { 1933 | filename: filename, 1934 | coverage: 0, 1935 | hits: 0, 1936 | misses: 0, 1937 | sloc: 0, 1938 | source: {} 1939 | }; 1940 | 1941 | data.source.forEach(function(line, num){ 1942 | num++; 1943 | 1944 | if (data[num] === 0) { 1945 | ret.misses++; 1946 | ret.sloc++; 1947 | } else if (data[num] !== undefined) { 1948 | ret.hits++; 1949 | ret.sloc++; 1950 | } 1951 | 1952 | ret.source[num] = { 1953 | source: line 1954 | , coverage: data[num] === undefined 1955 | ? '' 1956 | : data[num] 1957 | }; 1958 | }); 1959 | 1960 | ret.coverage = ret.hits / ret.sloc * 100; 1961 | 1962 | return ret; 1963 | } 1964 | 1965 | /** 1966 | * Return a plain-object representation of `test` 1967 | * free of cyclic properties etc. 1968 | * 1969 | * @param {Object} test 1970 | * @return {Object} 1971 | * @api private 1972 | */ 1973 | 1974 | function clean(test) { 1975 | return { 1976 | title: test.title 1977 | , fullTitle: test.fullTitle() 1978 | , duration: test.duration 1979 | } 1980 | } 1981 | 1982 | }); // module: reporters/json-cov.js 1983 | 1984 | require.register("reporters/json-stream.js", function(module, exports, require){ 1985 | 1986 | /** 1987 | * Module dependencies. 1988 | */ 1989 | 1990 | var Base = require('./base') 1991 | , color = Base.color; 1992 | 1993 | /** 1994 | * Expose `List`. 1995 | */ 1996 | 1997 | exports = module.exports = List; 1998 | 1999 | /** 2000 | * Initialize a new `List` test reporter. 2001 | * 2002 | * @param {Runner} runner 2003 | * @api public 2004 | */ 2005 | 2006 | function List(runner) { 2007 | Base.call(this, runner); 2008 | 2009 | var self = this 2010 | , stats = this.stats 2011 | , total = runner.total; 2012 | 2013 | runner.on('start', function(){ 2014 | console.log(JSON.stringify(['start', { total: total }])); 2015 | }); 2016 | 2017 | runner.on('pass', function(test){ 2018 | console.log(JSON.stringify(['pass', clean(test)])); 2019 | }); 2020 | 2021 | runner.on('fail', function(test, err){ 2022 | console.log(JSON.stringify(['fail', clean(test)])); 2023 | }); 2024 | 2025 | runner.on('end', function(){ 2026 | process.stdout.write(JSON.stringify(['end', self.stats])); 2027 | }); 2028 | } 2029 | 2030 | /** 2031 | * Return a plain-object representation of `test` 2032 | * free of cyclic properties etc. 2033 | * 2034 | * @param {Object} test 2035 | * @return {Object} 2036 | * @api private 2037 | */ 2038 | 2039 | function clean(test) { 2040 | return { 2041 | title: test.title 2042 | , fullTitle: test.fullTitle() 2043 | , duration: test.duration 2044 | } 2045 | } 2046 | }); // module: reporters/json-stream.js 2047 | 2048 | require.register("reporters/json.js", function(module, exports, require){ 2049 | 2050 | /** 2051 | * Module dependencies. 2052 | */ 2053 | 2054 | var Base = require('./base') 2055 | , cursor = Base.cursor 2056 | , color = Base.color; 2057 | 2058 | /** 2059 | * Expose `JSON`. 2060 | */ 2061 | 2062 | exports = module.exports = JSONReporter; 2063 | 2064 | /** 2065 | * Initialize a new `JSON` reporter. 2066 | * 2067 | * @param {Runner} runner 2068 | * @api public 2069 | */ 2070 | 2071 | function JSONReporter(runner) { 2072 | var self = this; 2073 | Base.call(this, runner); 2074 | 2075 | var tests = [] 2076 | , failures = [] 2077 | , passes = []; 2078 | 2079 | runner.on('test end', function(test){ 2080 | tests.push(test); 2081 | }); 2082 | 2083 | runner.on('pass', function(test){ 2084 | passes.push(test); 2085 | }); 2086 | 2087 | runner.on('fail', function(test){ 2088 | failures.push(test); 2089 | }); 2090 | 2091 | runner.on('end', function(){ 2092 | var obj = { 2093 | stats: self.stats 2094 | , tests: tests.map(clean) 2095 | , failures: failures.map(clean) 2096 | , passes: passes.map(clean) 2097 | }; 2098 | 2099 | process.stdout.write(JSON.stringify(obj, null, 2)); 2100 | }); 2101 | } 2102 | 2103 | /** 2104 | * Return a plain-object representation of `test` 2105 | * free of cyclic properties etc. 2106 | * 2107 | * @param {Object} test 2108 | * @return {Object} 2109 | * @api private 2110 | */ 2111 | 2112 | function clean(test) { 2113 | return { 2114 | title: test.title 2115 | , fullTitle: test.fullTitle() 2116 | , duration: test.duration 2117 | } 2118 | } 2119 | }); // module: reporters/json.js 2120 | 2121 | require.register("reporters/landing.js", function(module, exports, require){ 2122 | 2123 | /** 2124 | * Module dependencies. 2125 | */ 2126 | 2127 | var Base = require('./base') 2128 | , cursor = Base.cursor 2129 | , color = Base.color; 2130 | 2131 | /** 2132 | * Expose `Landing`. 2133 | */ 2134 | 2135 | exports = module.exports = Landing; 2136 | 2137 | /** 2138 | * Airplane color. 2139 | */ 2140 | 2141 | Base.colors.plane = 0; 2142 | 2143 | /** 2144 | * Airplane crash color. 2145 | */ 2146 | 2147 | Base.colors['plane crash'] = 31; 2148 | 2149 | /** 2150 | * Runway color. 2151 | */ 2152 | 2153 | Base.colors.runway = 90; 2154 | 2155 | /** 2156 | * Initialize a new `Landing` reporter. 2157 | * 2158 | * @param {Runner} runner 2159 | * @api public 2160 | */ 2161 | 2162 | function Landing(runner) { 2163 | Base.call(this, runner); 2164 | 2165 | var self = this 2166 | , stats = this.stats 2167 | , width = Base.window.width * .75 | 0 2168 | , total = runner.total 2169 | , stream = process.stdout 2170 | , plane = color('plane', '✈') 2171 | , crashed = -1 2172 | , n = 0; 2173 | 2174 | function runway() { 2175 | var buf = Array(width).join('-'); 2176 | return ' ' + color('runway', buf); 2177 | } 2178 | 2179 | runner.on('start', function(){ 2180 | stream.write('\n '); 2181 | cursor.hide(); 2182 | }); 2183 | 2184 | runner.on('test end', function(test){ 2185 | // check if the plane crashed 2186 | var col = -1 == crashed 2187 | ? width * ++n / total | 0 2188 | : crashed; 2189 | 2190 | // show the crash 2191 | if ('failed' == test.state) { 2192 | plane = color('plane crash', '✈'); 2193 | crashed = col; 2194 | } 2195 | 2196 | // render landing strip 2197 | stream.write('\033[4F\n\n'); 2198 | stream.write(runway()); 2199 | stream.write('\n '); 2200 | stream.write(color('runway', Array(col).join('⋅'))); 2201 | stream.write(plane) 2202 | stream.write(color('runway', Array(width - col).join('⋅') + '\n')); 2203 | stream.write(runway()); 2204 | stream.write('\033[0m'); 2205 | }); 2206 | 2207 | runner.on('end', function(){ 2208 | cursor.show(); 2209 | console.log(); 2210 | self.epilogue(); 2211 | }); 2212 | } 2213 | 2214 | /** 2215 | * Inherit from `Base.prototype`. 2216 | */ 2217 | 2218 | Landing.prototype = new Base; 2219 | Landing.prototype.constructor = Landing; 2220 | 2221 | }); // module: reporters/landing.js 2222 | 2223 | require.register("reporters/list.js", function(module, exports, require){ 2224 | 2225 | /** 2226 | * Module dependencies. 2227 | */ 2228 | 2229 | var Base = require('./base') 2230 | , cursor = Base.cursor 2231 | , color = Base.color; 2232 | 2233 | /** 2234 | * Expose `List`. 2235 | */ 2236 | 2237 | exports = module.exports = List; 2238 | 2239 | /** 2240 | * Initialize a new `List` test reporter. 2241 | * 2242 | * @param {Runner} runner 2243 | * @api public 2244 | */ 2245 | 2246 | function List(runner) { 2247 | Base.call(this, runner); 2248 | 2249 | var self = this 2250 | , stats = this.stats 2251 | , n = 0; 2252 | 2253 | runner.on('start', function(){ 2254 | console.log(); 2255 | }); 2256 | 2257 | runner.on('test', function(test){ 2258 | process.stdout.write(color('pass', ' ' + test.fullTitle() + ': ')); 2259 | }); 2260 | 2261 | runner.on('pending', function(test){ 2262 | var fmt = color('checkmark', ' -') 2263 | + color('pending', ' %s'); 2264 | console.log(fmt, test.fullTitle()); 2265 | }); 2266 | 2267 | runner.on('pass', function(test){ 2268 | var fmt = color('checkmark', ' ✓') 2269 | + color('pass', ' %s: ') 2270 | + color(test.speed, '%dms'); 2271 | cursor.CR(); 2272 | console.log(fmt, test.fullTitle(), test.duration); 2273 | }); 2274 | 2275 | runner.on('fail', function(test, err){ 2276 | cursor.CR(); 2277 | console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); 2278 | }); 2279 | 2280 | runner.on('end', self.epilogue.bind(self)); 2281 | } 2282 | 2283 | /** 2284 | * Inherit from `Base.prototype`. 2285 | */ 2286 | 2287 | List.prototype = new Base; 2288 | List.prototype.constructor = List; 2289 | 2290 | 2291 | }); // module: reporters/list.js 2292 | 2293 | require.register("reporters/markdown.js", function(module, exports, require){ 2294 | /** 2295 | * Module dependencies. 2296 | */ 2297 | 2298 | var Base = require('./base') 2299 | , utils = require('../utils'); 2300 | 2301 | /** 2302 | * Expose `Markdown`. 2303 | */ 2304 | 2305 | exports = module.exports = Markdown; 2306 | 2307 | /** 2308 | * Initialize a new `Markdown` reporter. 2309 | * 2310 | * @param {Runner} runner 2311 | * @api public 2312 | */ 2313 | 2314 | function Markdown(runner) { 2315 | Base.call(this, runner); 2316 | 2317 | var self = this 2318 | , stats = this.stats 2319 | , total = runner.total 2320 | , level = 0 2321 | , buf = ''; 2322 | 2323 | function title(str) { 2324 | return Array(level).join('#') + ' ' + str; 2325 | } 2326 | 2327 | function indent() { 2328 | return Array(level).join(' '); 2329 | } 2330 | 2331 | function mapTOC(suite, obj) { 2332 | var ret = obj; 2333 | obj = obj[suite.title] = obj[suite.title] || { suite: suite }; 2334 | suite.suites.forEach(function(suite){ 2335 | mapTOC(suite, obj); 2336 | }); 2337 | return ret; 2338 | } 2339 | 2340 | function stringifyTOC(obj, level) { 2341 | ++level; 2342 | var buf = ''; 2343 | var link; 2344 | for (var key in obj) { 2345 | if ('suite' == key) continue; 2346 | if (key) link = ' - [' + key + '](#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; 2347 | if (key) buf += Array(level).join(' ') + link; 2348 | buf += stringifyTOC(obj[key], level); 2349 | } 2350 | --level; 2351 | return buf; 2352 | } 2353 | 2354 | function generateTOC(suite) { 2355 | var obj = mapTOC(suite, {}); 2356 | return stringifyTOC(obj, 0); 2357 | } 2358 | 2359 | generateTOC(runner.suite); 2360 | 2361 | runner.on('suite', function(suite){ 2362 | ++level; 2363 | var slug = utils.slug(suite.fullTitle()); 2364 | buf += '' + '\n'; 2365 | buf += title(suite.title) + '\n'; 2366 | }); 2367 | 2368 | runner.on('suite end', function(suite){ 2369 | --level; 2370 | }); 2371 | 2372 | runner.on('pass', function(test){ 2373 | var code = utils.clean(test.fn.toString()); 2374 | buf += test.title + '.\n'; 2375 | buf += '\n```js\n'; 2376 | buf += code + '\n'; 2377 | buf += '```\n\n'; 2378 | }); 2379 | 2380 | runner.on('end', function(){ 2381 | process.stdout.write('# TOC\n'); 2382 | process.stdout.write(generateTOC(runner.suite)); 2383 | process.stdout.write(buf); 2384 | }); 2385 | } 2386 | }); // module: reporters/markdown.js 2387 | 2388 | require.register("reporters/min.js", function(module, exports, require){ 2389 | 2390 | /** 2391 | * Module dependencies. 2392 | */ 2393 | 2394 | var Base = require('./base'); 2395 | 2396 | /** 2397 | * Expose `Min`. 2398 | */ 2399 | 2400 | exports = module.exports = Min; 2401 | 2402 | /** 2403 | * Initialize a new `Min` minimal test reporter (best used with --watch). 2404 | * 2405 | * @param {Runner} runner 2406 | * @api public 2407 | */ 2408 | 2409 | function Min(runner) { 2410 | Base.call(this, runner); 2411 | 2412 | runner.on('start', function(){ 2413 | // clear screen 2414 | process.stdout.write('\033[2J'); 2415 | // set cursor position 2416 | process.stdout.write('\033[1;3H'); 2417 | }); 2418 | 2419 | runner.on('end', this.epilogue.bind(this)); 2420 | } 2421 | 2422 | /** 2423 | * Inherit from `Base.prototype`. 2424 | */ 2425 | 2426 | Min.prototype = new Base; 2427 | Min.prototype.constructor = Min; 2428 | 2429 | }); // module: reporters/min.js 2430 | 2431 | require.register("reporters/nyan.js", function(module, exports, require){ 2432 | 2433 | /** 2434 | * Module dependencies. 2435 | */ 2436 | 2437 | var Base = require('./base') 2438 | , color = Base.color; 2439 | 2440 | /** 2441 | * Expose `Dot`. 2442 | */ 2443 | 2444 | exports = module.exports = NyanCat; 2445 | 2446 | /** 2447 | * Initialize a new `Dot` matrix test reporter. 2448 | * 2449 | * @param {Runner} runner 2450 | * @api public 2451 | */ 2452 | 2453 | function NyanCat(runner) { 2454 | Base.call(this, runner); 2455 | 2456 | var self = this 2457 | , stats = this.stats 2458 | , width = Base.window.width * .75 | 0 2459 | , rainbowColors = this.rainbowColors = self.generateColors() 2460 | , colorIndex = this.colorIndex = 0 2461 | , numerOfLines = this.numberOfLines = 4 2462 | , trajectories = this.trajectories = [[], [], [], []] 2463 | , nyanCatWidth = this.nyanCatWidth = 11 2464 | , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth) 2465 | , scoreboardWidth = this.scoreboardWidth = 5 2466 | , tick = this.tick = 0 2467 | , n = 0; 2468 | 2469 | runner.on('start', function(){ 2470 | Base.cursor.hide(); 2471 | self.draw('start'); 2472 | }); 2473 | 2474 | runner.on('pending', function(test){ 2475 | self.draw('pending'); 2476 | }); 2477 | 2478 | runner.on('pass', function(test){ 2479 | self.draw('pass'); 2480 | }); 2481 | 2482 | runner.on('fail', function(test, err){ 2483 | self.draw('fail'); 2484 | }); 2485 | 2486 | runner.on('end', function(){ 2487 | Base.cursor.show(); 2488 | for (var i = 0; i < self.numberOfLines; i++) write('\n'); 2489 | self.epilogue(); 2490 | }); 2491 | } 2492 | 2493 | /** 2494 | * Draw the nyan cat with runner `status`. 2495 | * 2496 | * @param {String} status 2497 | * @api private 2498 | */ 2499 | 2500 | NyanCat.prototype.draw = function(status){ 2501 | this.appendRainbow(); 2502 | this.drawScoreboard(); 2503 | this.drawRainbow(); 2504 | this.drawNyanCat(status); 2505 | this.tick = !this.tick; 2506 | }; 2507 | 2508 | /** 2509 | * Draw the "scoreboard" showing the number 2510 | * of passes, failures and pending tests. 2511 | * 2512 | * @api private 2513 | */ 2514 | 2515 | NyanCat.prototype.drawScoreboard = function(){ 2516 | var stats = this.stats; 2517 | var colors = Base.colors; 2518 | 2519 | function draw(color, n) { 2520 | write(' '); 2521 | write('\033[' + color + 'm' + n + '\033[0m'); 2522 | write('\n'); 2523 | } 2524 | 2525 | draw(colors.green, stats.passes); 2526 | draw(colors.fail, stats.failures); 2527 | draw(colors.pending, stats.pending); 2528 | write('\n'); 2529 | 2530 | this.cursorUp(this.numberOfLines); 2531 | }; 2532 | 2533 | /** 2534 | * Append the rainbow. 2535 | * 2536 | * @api private 2537 | */ 2538 | 2539 | NyanCat.prototype.appendRainbow = function(){ 2540 | var segment = this.tick ? '_' : '-'; 2541 | var rainbowified = this.rainbowify(segment); 2542 | 2543 | for (var index = 0; index < this.numberOfLines; index++) { 2544 | var trajectory = this.trajectories[index]; 2545 | if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift(); 2546 | trajectory.push(rainbowified); 2547 | } 2548 | }; 2549 | 2550 | /** 2551 | * Draw the rainbow. 2552 | * 2553 | * @api private 2554 | */ 2555 | 2556 | NyanCat.prototype.drawRainbow = function(){ 2557 | var self = this; 2558 | 2559 | this.trajectories.forEach(function(line, index) { 2560 | write('\033[' + self.scoreboardWidth + 'C'); 2561 | write(line.join('')); 2562 | write('\n'); 2563 | }); 2564 | 2565 | this.cursorUp(this.numberOfLines); 2566 | }; 2567 | 2568 | /** 2569 | * Draw the nyan cat with `status`. 2570 | * 2571 | * @param {String} status 2572 | * @api private 2573 | */ 2574 | 2575 | NyanCat.prototype.drawNyanCat = function(status) { 2576 | var self = this; 2577 | var startWidth = this.scoreboardWidth + this.trajectories[0].length; 2578 | 2579 | [0, 1, 2, 3].forEach(function(index) { 2580 | write('\033[' + startWidth + 'C'); 2581 | 2582 | switch (index) { 2583 | case 0: 2584 | write('_,------,'); 2585 | write('\n'); 2586 | break; 2587 | case 1: 2588 | var padding = self.tick ? ' ' : ' '; 2589 | write('_|' + padding + '/\\_/\\ '); 2590 | write('\n'); 2591 | break; 2592 | case 2: 2593 | var padding = self.tick ? '_' : '__'; 2594 | var tail = self.tick ? '~' : '^'; 2595 | var face; 2596 | switch (status) { 2597 | case 'pass': 2598 | face = '( ^ .^)'; 2599 | break; 2600 | case 'fail': 2601 | face = '( o .o)'; 2602 | break; 2603 | default: 2604 | face = '( - .-)'; 2605 | } 2606 | write(tail + '|' + padding + face + ' '); 2607 | write('\n'); 2608 | break; 2609 | case 3: 2610 | var padding = self.tick ? ' ' : ' '; 2611 | write(padding + '"" "" '); 2612 | write('\n'); 2613 | break; 2614 | } 2615 | }); 2616 | 2617 | this.cursorUp(this.numberOfLines); 2618 | }; 2619 | 2620 | /** 2621 | * Move cursor up `n`. 2622 | * 2623 | * @param {Number} n 2624 | * @api private 2625 | */ 2626 | 2627 | NyanCat.prototype.cursorUp = function(n) { 2628 | write('\033[' + n + 'A'); 2629 | }; 2630 | 2631 | /** 2632 | * Move cursor down `n`. 2633 | * 2634 | * @param {Number} n 2635 | * @api private 2636 | */ 2637 | 2638 | NyanCat.prototype.cursorDown = function(n) { 2639 | write('\033[' + n + 'B'); 2640 | }; 2641 | 2642 | /** 2643 | * Generate rainbow colors. 2644 | * 2645 | * @return {Array} 2646 | * @api private 2647 | */ 2648 | 2649 | NyanCat.prototype.generateColors = function(){ 2650 | var colors = []; 2651 | 2652 | for (var i = 0; i < (6 * 7); i++) { 2653 | var pi3 = Math.floor(Math.PI / 3); 2654 | var n = (i * (1.0 / 6)); 2655 | var r = Math.floor(3 * Math.sin(n) + 3); 2656 | var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); 2657 | var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); 2658 | colors.push(36 * r + 6 * g + b + 16); 2659 | } 2660 | 2661 | return colors; 2662 | }; 2663 | 2664 | /** 2665 | * Apply rainbow to the given `str`. 2666 | * 2667 | * @param {String} str 2668 | * @return {String} 2669 | * @api private 2670 | */ 2671 | 2672 | NyanCat.prototype.rainbowify = function(str){ 2673 | var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length]; 2674 | this.colorIndex += 1; 2675 | return '\033[38;5;' + color + 'm' + str + '\033[0m'; 2676 | }; 2677 | 2678 | /** 2679 | * Stdout helper. 2680 | */ 2681 | 2682 | function write(string) { 2683 | process.stdout.write(string); 2684 | } 2685 | 2686 | /** 2687 | * Inherit from `Base.prototype`. 2688 | */ 2689 | 2690 | NyanCat.prototype = new Base; 2691 | NyanCat.prototype.constructor = NyanCat; 2692 | 2693 | 2694 | }); // module: reporters/nyan.js 2695 | 2696 | require.register("reporters/progress.js", function(module, exports, require){ 2697 | 2698 | /** 2699 | * Module dependencies. 2700 | */ 2701 | 2702 | var Base = require('./base') 2703 | , cursor = Base.cursor 2704 | , color = Base.color; 2705 | 2706 | /** 2707 | * Expose `Progress`. 2708 | */ 2709 | 2710 | exports = module.exports = Progress; 2711 | 2712 | /** 2713 | * General progress bar color. 2714 | */ 2715 | 2716 | Base.colors.progress = 90; 2717 | 2718 | /** 2719 | * Initialize a new `Progress` bar test reporter. 2720 | * 2721 | * @param {Runner} runner 2722 | * @param {Object} options 2723 | * @api public 2724 | */ 2725 | 2726 | function Progress(runner, options) { 2727 | Base.call(this, runner); 2728 | 2729 | var self = this 2730 | , options = options || {} 2731 | , stats = this.stats 2732 | , width = Base.window.width * .50 | 0 2733 | , total = runner.total 2734 | , complete = 0 2735 | , max = Math.max; 2736 | 2737 | // default chars 2738 | options.open = options.open || '['; 2739 | options.complete = options.complete || '▬'; 2740 | options.incomplete = options.incomplete || '⋅'; 2741 | options.close = options.close || ']'; 2742 | options.verbose = false; 2743 | 2744 | // tests started 2745 | runner.on('start', function(){ 2746 | console.log(); 2747 | cursor.hide(); 2748 | }); 2749 | 2750 | // tests complete 2751 | runner.on('test end', function(){ 2752 | complete++; 2753 | var incomplete = total - complete 2754 | , percent = complete / total 2755 | , n = width * percent | 0 2756 | , i = width - n; 2757 | 2758 | cursor.CR(); 2759 | process.stdout.write('\033[J'); 2760 | process.stdout.write(color('progress', ' ' + options.open)); 2761 | process.stdout.write(Array(n).join(options.complete)); 2762 | process.stdout.write(Array(i).join(options.incomplete)); 2763 | process.stdout.write(color('progress', options.close)); 2764 | if (options.verbose) { 2765 | process.stdout.write(color('progress', ' ' + complete + ' of ' + total)); 2766 | } 2767 | }); 2768 | 2769 | // tests are complete, output some stats 2770 | // and the failures if any 2771 | runner.on('end', function(){ 2772 | cursor.show(); 2773 | console.log(); 2774 | self.epilogue(); 2775 | }); 2776 | } 2777 | 2778 | /** 2779 | * Inherit from `Base.prototype`. 2780 | */ 2781 | 2782 | Progress.prototype = new Base; 2783 | Progress.prototype.constructor = Progress; 2784 | 2785 | 2786 | }); // module: reporters/progress.js 2787 | 2788 | require.register("reporters/spec.js", function(module, exports, require){ 2789 | 2790 | /** 2791 | * Module dependencies. 2792 | */ 2793 | 2794 | var Base = require('./base') 2795 | , cursor = Base.cursor 2796 | , color = Base.color; 2797 | 2798 | /** 2799 | * Expose `Spec`. 2800 | */ 2801 | 2802 | exports = module.exports = Spec; 2803 | 2804 | /** 2805 | * Initialize a new `Spec` test reporter. 2806 | * 2807 | * @param {Runner} runner 2808 | * @api public 2809 | */ 2810 | 2811 | function Spec(runner) { 2812 | Base.call(this, runner); 2813 | 2814 | var self = this 2815 | , stats = this.stats 2816 | , indents = 0 2817 | , n = 0; 2818 | 2819 | function indent() { 2820 | return Array(indents).join(' ') 2821 | } 2822 | 2823 | runner.on('start', function(){ 2824 | console.log(); 2825 | }); 2826 | 2827 | runner.on('suite', function(suite){ 2828 | ++indents; 2829 | console.log(color('suite', '%s%s'), indent(), suite.title); 2830 | }); 2831 | 2832 | runner.on('suite end', function(suite){ 2833 | --indents; 2834 | if (1 == indents) console.log(); 2835 | }); 2836 | 2837 | runner.on('test', function(test){ 2838 | process.stdout.write(indent() + color('pass', ' ◦ ' + test.title + ': ')); 2839 | }); 2840 | 2841 | runner.on('pending', function(test){ 2842 | var fmt = indent() + color('pending', ' - %s'); 2843 | console.log(fmt, test.title); 2844 | }); 2845 | 2846 | runner.on('pass', function(test){ 2847 | if ('fast' == test.speed) { 2848 | var fmt = indent() 2849 | + color('checkmark', ' ✓') 2850 | + color('pass', ' %s '); 2851 | cursor.CR(); 2852 | console.log(fmt, test.title); 2853 | } else { 2854 | var fmt = indent() 2855 | + color('checkmark', ' ✓') 2856 | + color('pass', ' %s ') 2857 | + color(test.speed, '(%dms)'); 2858 | cursor.CR(); 2859 | console.log(fmt, test.title, test.duration); 2860 | } 2861 | }); 2862 | 2863 | runner.on('fail', function(test, err){ 2864 | cursor.CR(); 2865 | console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); 2866 | }); 2867 | 2868 | runner.on('end', self.epilogue.bind(self)); 2869 | } 2870 | 2871 | /** 2872 | * Inherit from `Base.prototype`. 2873 | */ 2874 | 2875 | Spec.prototype = new Base; 2876 | Spec.prototype.constructor = Spec; 2877 | 2878 | 2879 | }); // module: reporters/spec.js 2880 | 2881 | require.register("reporters/tap.js", function(module, exports, require){ 2882 | 2883 | /** 2884 | * Module dependencies. 2885 | */ 2886 | 2887 | var Base = require('./base') 2888 | , cursor = Base.cursor 2889 | , color = Base.color; 2890 | 2891 | /** 2892 | * Expose `TAP`. 2893 | */ 2894 | 2895 | exports = module.exports = TAP; 2896 | 2897 | /** 2898 | * Initialize a new `TAP` reporter. 2899 | * 2900 | * @param {Runner} runner 2901 | * @api public 2902 | */ 2903 | 2904 | function TAP(runner) { 2905 | Base.call(this, runner); 2906 | 2907 | var self = this 2908 | , stats = this.stats 2909 | , total = runner.total 2910 | , n = 1; 2911 | 2912 | runner.on('start', function(){ 2913 | console.log('%d..%d', 1, total); 2914 | }); 2915 | 2916 | runner.on('test end', function(){ 2917 | ++n; 2918 | }); 2919 | 2920 | runner.on('pending', function(test){ 2921 | console.log('ok %d %s # SKIP -', n, title(test)); 2922 | }); 2923 | 2924 | runner.on('pass', function(test){ 2925 | console.log('ok %d %s', n, title(test)); 2926 | }); 2927 | 2928 | runner.on('fail', function(test, err){ 2929 | console.log('not ok %d %s', n, title(test)); 2930 | console.log(err.stack.replace(/^/gm, ' ')); 2931 | }); 2932 | } 2933 | 2934 | /** 2935 | * Return a TAP-safe title of `test` 2936 | * 2937 | * @param {Object} test 2938 | * @return {String} 2939 | * @api private 2940 | */ 2941 | 2942 | function title(test) { 2943 | return test.fullTitle().replace(/#/g, ''); 2944 | } 2945 | 2946 | }); // module: reporters/tap.js 2947 | 2948 | require.register("reporters/teamcity.js", function(module, exports, require){ 2949 | 2950 | /** 2951 | * Module dependencies. 2952 | */ 2953 | 2954 | var Base = require('./base'); 2955 | 2956 | /** 2957 | * Expose `Teamcity`. 2958 | */ 2959 | 2960 | exports = module.exports = Teamcity; 2961 | 2962 | /** 2963 | * Initialize a new `Teamcity` reporter. 2964 | * 2965 | * @param {Runner} runner 2966 | * @api public 2967 | */ 2968 | 2969 | function Teamcity(runner) { 2970 | Base.call(this, runner); 2971 | var stats = this.stats; 2972 | 2973 | runner.on('start', function() { 2974 | console.log("##teamcity[testSuiteStarted name='mocha.suite']"); 2975 | }); 2976 | 2977 | runner.on('test', function(test) { 2978 | console.log("##teamcity[testStarted name='" + escape(test.fullTitle()) + "']"); 2979 | }); 2980 | 2981 | runner.on('fail', function(test, err) { 2982 | console.log("##teamcity[testFailed name='" + escape(test.fullTitle()) + "' message='" + escape(err.message) + "']"); 2983 | }); 2984 | 2985 | runner.on('pending', function(test) { 2986 | console.log("##teamcity[testIgnored name='" + escape(test.fullTitle()) + "' message='pending']"); 2987 | }); 2988 | 2989 | runner.on('test end', function(test) { 2990 | console.log("##teamcity[testFinished name='" + escape(test.fullTitle()) + "' duration='" + test.duration + "']"); 2991 | }); 2992 | 2993 | runner.on('end', function() { 2994 | console.log("##teamcity[testSuiteFinished name='mocha.suite' duration='" + stats.duration + "']"); 2995 | }); 2996 | } 2997 | 2998 | /** 2999 | * Escape the given `str`. 3000 | */ 3001 | 3002 | function escape(str) { 3003 | return str 3004 | .replace(/\|/g, "||") 3005 | .replace(/\n/g, "|n") 3006 | .replace(/\r/g, "|r") 3007 | .replace(/\[/g, "|[") 3008 | .replace(/\]/g, "|]") 3009 | .replace(/\u0085/g, "|x") 3010 | .replace(/\u2028/g, "|l") 3011 | .replace(/\u2029/g, "|p") 3012 | .replace(/'/g, "|'"); 3013 | } 3014 | 3015 | }); // module: reporters/teamcity.js 3016 | 3017 | require.register("reporters/xunit.js", function(module, exports, require){ 3018 | 3019 | /** 3020 | * Module dependencies. 3021 | */ 3022 | 3023 | var Base = require('./base') 3024 | , utils = require('../utils') 3025 | , escape = utils.escape; 3026 | 3027 | /** 3028 | * Save timer references to avoid Sinon interfering (see GH-237). 3029 | */ 3030 | 3031 | var Date = global.Date 3032 | , setTimeout = global.setTimeout 3033 | , setInterval = global.setInterval 3034 | , clearTimeout = global.clearTimeout 3035 | , clearInterval = global.clearInterval; 3036 | 3037 | /** 3038 | * Expose `XUnit`. 3039 | */ 3040 | 3041 | exports = module.exports = XUnit; 3042 | 3043 | /** 3044 | * Initialize a new `XUnit` reporter. 3045 | * 3046 | * @param {Runner} runner 3047 | * @api public 3048 | */ 3049 | 3050 | function XUnit(runner) { 3051 | Base.call(this, runner); 3052 | var stats = this.stats 3053 | , tests = [] 3054 | , self = this; 3055 | 3056 | runner.on('test end', function(test){ 3057 | tests.push(test); 3058 | }); 3059 | 3060 | runner.on('end', function(){ 3061 | console.log(tag('testsuite', { 3062 | name: 'Mocha Tests' 3063 | , tests: stats.tests 3064 | , failures: stats.failures 3065 | , errors: stats.failures 3066 | , skip: stats.tests - stats.failures - stats.passes 3067 | , timestamp: (new Date).toUTCString() 3068 | , time: stats.duration / 1000 3069 | }, false)); 3070 | 3071 | tests.forEach(test); 3072 | console.log(''); 3073 | }); 3074 | } 3075 | 3076 | /** 3077 | * Inherit from `Base.prototype`. 3078 | */ 3079 | 3080 | XUnit.prototype = new Base; 3081 | XUnit.prototype.constructor = XUnit; 3082 | 3083 | 3084 | /** 3085 | * Output tag for the given `test.` 3086 | */ 3087 | 3088 | function test(test) { 3089 | var attrs = { 3090 | classname: test.parent.fullTitle() 3091 | , name: test.title 3092 | , time: test.duration / 1000 3093 | }; 3094 | 3095 | if ('failed' == test.state) { 3096 | var err = test.err; 3097 | attrs.message = escape(err.message); 3098 | console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack)))); 3099 | } else if (test.pending) { 3100 | console.log(tag('testcase', attrs, false, tag('skipped', {}, true))); 3101 | } else { 3102 | console.log(tag('testcase', attrs, true) ); 3103 | } 3104 | } 3105 | 3106 | /** 3107 | * HTML tag helper. 3108 | */ 3109 | 3110 | function tag(name, attrs, close, content) { 3111 | var end = close ? '/>' : '>' 3112 | , pairs = [] 3113 | , tag; 3114 | 3115 | for (var key in attrs) { 3116 | pairs.push(key + '="' + escape(attrs[key]) + '"'); 3117 | } 3118 | 3119 | tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; 3120 | if (content) tag += content + ''; 3130 | } 3131 | 3132 | }); // module: reporters/xunit.js 3133 | 3134 | require.register("runnable.js", function(module, exports, require){ 3135 | 3136 | /** 3137 | * Module dependencies. 3138 | */ 3139 | 3140 | var EventEmitter = require('browser/events').EventEmitter 3141 | , debug = require('browser/debug')('runnable'); 3142 | 3143 | /** 3144 | * Save timer references to avoid Sinon interfering (see GH-237). 3145 | */ 3146 | 3147 | var Date = global.Date 3148 | , setTimeout = global.setTimeout 3149 | , setInterval = global.setInterval 3150 | , clearTimeout = global.clearTimeout 3151 | , clearInterval = global.clearInterval; 3152 | 3153 | /** 3154 | * Expose `Runnable`. 3155 | */ 3156 | 3157 | module.exports = Runnable; 3158 | 3159 | /** 3160 | * Initialize a new `Runnable` with the given `title` and callback `fn`. 3161 | * 3162 | * @param {String} title 3163 | * @param {Function} fn 3164 | * @api private 3165 | */ 3166 | 3167 | function Runnable(title, fn) { 3168 | this.title = title; 3169 | this.fn = fn; 3170 | this.async = fn && fn.length; 3171 | this.sync = ! this.async; 3172 | this._timeout = 2000; 3173 | this.timedOut = false; 3174 | } 3175 | 3176 | /** 3177 | * Inherit from `EventEmitter.prototype`. 3178 | */ 3179 | 3180 | Runnable.prototype = new EventEmitter; 3181 | Runnable.prototype.constructor = Runnable; 3182 | 3183 | 3184 | /** 3185 | * Set & get timeout `ms`. 3186 | * 3187 | * @param {Number} ms 3188 | * @return {Runnable|Number} ms or self 3189 | * @api private 3190 | */ 3191 | 3192 | Runnable.prototype.timeout = function(ms){ 3193 | if (0 == arguments.length) return this._timeout; 3194 | debug('timeout %d', ms); 3195 | this._timeout = ms; 3196 | if (this.timer) this.resetTimeout(); 3197 | return this; 3198 | }; 3199 | 3200 | /** 3201 | * Return the full title generated by recursively 3202 | * concatenating the parent's full title. 3203 | * 3204 | * @return {String} 3205 | * @api public 3206 | */ 3207 | 3208 | Runnable.prototype.fullTitle = function(){ 3209 | return this.parent.fullTitle() + ' ' + this.title; 3210 | }; 3211 | 3212 | /** 3213 | * Clear the timeout. 3214 | * 3215 | * @api private 3216 | */ 3217 | 3218 | Runnable.prototype.clearTimeout = function(){ 3219 | clearTimeout(this.timer); 3220 | }; 3221 | 3222 | /** 3223 | * Inspect the runnable void of private properties. 3224 | * 3225 | * @return {String} 3226 | * @api private 3227 | */ 3228 | 3229 | Runnable.prototype.inspect = function(){ 3230 | return JSON.stringify(this, function(key, val){ 3231 | if ('_' == key[0]) return; 3232 | if ('parent' == key) return '#'; 3233 | if ('ctx' == key) return '#'; 3234 | return val; 3235 | }, 2); 3236 | }; 3237 | 3238 | /** 3239 | * Reset the timeout. 3240 | * 3241 | * @api private 3242 | */ 3243 | 3244 | Runnable.prototype.resetTimeout = function(){ 3245 | var self = this 3246 | , ms = this.timeout(); 3247 | 3248 | this.clearTimeout(); 3249 | if (ms) { 3250 | this.timer = setTimeout(function(){ 3251 | self.callback(new Error('timeout of ' + ms + 'ms exceeded')); 3252 | self.timedOut = true; 3253 | }, ms); 3254 | } 3255 | }; 3256 | 3257 | /** 3258 | * Run the test and invoke `fn(err)`. 3259 | * 3260 | * @param {Function} fn 3261 | * @api private 3262 | */ 3263 | 3264 | Runnable.prototype.run = function(fn){ 3265 | var self = this 3266 | , ms = this.timeout() 3267 | , start = new Date 3268 | , ctx = this.ctx 3269 | , finished 3270 | , emitted; 3271 | 3272 | if (ctx) ctx.runnable(this); 3273 | 3274 | // timeout 3275 | if (this.async) { 3276 | if (ms) { 3277 | this.timer = setTimeout(function(){ 3278 | done(new Error('timeout of ' + ms + 'ms exceeded')); 3279 | self.timedOut = true; 3280 | }, ms); 3281 | } 3282 | } 3283 | 3284 | // called multiple times 3285 | function multiple(err) { 3286 | if (emitted) return; 3287 | emitted = true; 3288 | self.emit('error', err || new Error('done() called multiple times')); 3289 | } 3290 | 3291 | // finished 3292 | function done(err) { 3293 | if (self.timedOut) return; 3294 | if (finished) return multiple(err); 3295 | self.clearTimeout(); 3296 | self.duration = new Date - start; 3297 | finished = true; 3298 | fn(err); 3299 | } 3300 | 3301 | // for .resetTimeout() 3302 | this.callback = done; 3303 | 3304 | // async 3305 | if (this.async) { 3306 | try { 3307 | this.fn.call(ctx, function(err){ 3308 | if (err instanceof Error) return done(err); 3309 | if (null != err) return done(new Error('done() invoked with non-Error: ' + err)); 3310 | done(); 3311 | }); 3312 | } catch (err) { 3313 | done(err); 3314 | } 3315 | return; 3316 | } 3317 | 3318 | // sync 3319 | try { 3320 | if (!this.pending) this.fn.call(ctx); 3321 | this.duration = new Date - start; 3322 | fn(); 3323 | } catch (err) { 3324 | fn(err); 3325 | } 3326 | }; 3327 | 3328 | }); // module: runnable.js 3329 | 3330 | require.register("runner.js", function(module, exports, require){ 3331 | 3332 | /** 3333 | * Module dependencies. 3334 | */ 3335 | 3336 | var EventEmitter = require('browser/events').EventEmitter 3337 | , debug = require('browser/debug')('runner') 3338 | , Test = require('./test') 3339 | , utils = require('./utils') 3340 | , filter = utils.filter 3341 | , keys = utils.keys 3342 | , noop = function(){}; 3343 | 3344 | /** 3345 | * Expose `Runner`. 3346 | */ 3347 | 3348 | module.exports = Runner; 3349 | 3350 | /** 3351 | * Initialize a `Runner` for the given `suite`. 3352 | * 3353 | * Events: 3354 | * 3355 | * - `start` execution started 3356 | * - `end` execution complete 3357 | * - `suite` (suite) test suite execution started 3358 | * - `suite end` (suite) all tests (and sub-suites) have finished 3359 | * - `test` (test) test execution started 3360 | * - `test end` (test) test completed 3361 | * - `hook` (hook) hook execution started 3362 | * - `hook end` (hook) hook complete 3363 | * - `pass` (test) test passed 3364 | * - `fail` (test, err) test failed 3365 | * 3366 | * @api public 3367 | */ 3368 | 3369 | function Runner(suite) { 3370 | var self = this; 3371 | this._globals = []; 3372 | this.suite = suite; 3373 | this.total = suite.total(); 3374 | this.failures = 0; 3375 | this.on('test end', function(test){ self.checkGlobals(test); }); 3376 | this.on('hook end', function(hook){ self.checkGlobals(hook); }); 3377 | this.grep(/.*/); 3378 | this.globals(utils.keys(global).concat(['errno'])); 3379 | } 3380 | 3381 | /** 3382 | * Inherit from `EventEmitter.prototype`. 3383 | */ 3384 | 3385 | Runner.prototype = new EventEmitter; 3386 | Runner.prototype.constructor = Runner; 3387 | 3388 | 3389 | /** 3390 | * Run tests with full titles matching `re`. Updates runner.total 3391 | * with number of tests matched. 3392 | * 3393 | * @param {RegExp} re 3394 | * @param {Boolean} invert 3395 | * @return {Runner} for chaining 3396 | * @api public 3397 | */ 3398 | 3399 | Runner.prototype.grep = function(re, invert){ 3400 | debug('grep %s', re); 3401 | this._grep = re; 3402 | this._invert = invert; 3403 | this.total = this.grepTotal(this.suite); 3404 | return this; 3405 | }; 3406 | 3407 | /** 3408 | * Returns the number of tests matching the grep search for the 3409 | * given suite. 3410 | * 3411 | * @param {Suite} suite 3412 | * @return {Number} 3413 | * @api public 3414 | */ 3415 | 3416 | Runner.prototype.grepTotal = function(suite) { 3417 | var self = this; 3418 | var total = 0; 3419 | 3420 | suite.eachTest(function(test){ 3421 | var match = self._grep.test(test.fullTitle()); 3422 | if (self._invert) match = !match; 3423 | if (match) total++; 3424 | }); 3425 | 3426 | return total; 3427 | }; 3428 | 3429 | /** 3430 | * Allow the given `arr` of globals. 3431 | * 3432 | * @param {Array} arr 3433 | * @return {Runner} for chaining 3434 | * @api public 3435 | */ 3436 | 3437 | Runner.prototype.globals = function(arr){ 3438 | if (0 == arguments.length) return this._globals; 3439 | debug('globals %j', arr); 3440 | utils.forEach(arr, function(arr){ 3441 | this._globals.push(arr); 3442 | }, this); 3443 | return this; 3444 | }; 3445 | 3446 | /** 3447 | * Check for global variable leaks. 3448 | * 3449 | * @api private 3450 | */ 3451 | 3452 | Runner.prototype.checkGlobals = function(test){ 3453 | if (this.ignoreLeaks) return; 3454 | var leaks = filterLeaks(this._globals); 3455 | 3456 | this._globals = this._globals.concat(leaks); 3457 | 3458 | if (leaks.length > 1) { 3459 | this.fail(test, new Error('global leaks detected: ' + leaks.join(', ') + '')); 3460 | } else if (leaks.length) { 3461 | this.fail(test, new Error('global leak detected: ' + leaks[0])); 3462 | } 3463 | }; 3464 | 3465 | /** 3466 | * Fail the given `test`. 3467 | * 3468 | * @param {Test} test 3469 | * @param {Error} err 3470 | * @api private 3471 | */ 3472 | 3473 | Runner.prototype.fail = function(test, err){ 3474 | ++this.failures; 3475 | test.state = 'failed'; 3476 | if ('string' == typeof err) { 3477 | err = new Error('the string "' + err + '" was thrown, throw an Error :)'); 3478 | } 3479 | this.emit('fail', test, err); 3480 | }; 3481 | 3482 | /** 3483 | * Fail the given `hook` with `err`. 3484 | * 3485 | * Hook failures (currently) hard-end due 3486 | * to that fact that a failing hook will 3487 | * surely cause subsequent tests to fail, 3488 | * causing jumbled reporting. 3489 | * 3490 | * @param {Hook} hook 3491 | * @param {Error} err 3492 | * @api private 3493 | */ 3494 | 3495 | Runner.prototype.failHook = function(hook, err){ 3496 | this.fail(hook, err); 3497 | this.emit('end'); 3498 | }; 3499 | 3500 | /** 3501 | * Run hook `name` callbacks and then invoke `fn()`. 3502 | * 3503 | * @param {String} name 3504 | * @param {Function} function 3505 | * @api private 3506 | */ 3507 | 3508 | Runner.prototype.hook = function(name, fn){ 3509 | var suite = this.suite 3510 | , hooks = suite['_' + name] 3511 | , ms = suite._timeout 3512 | , self = this 3513 | , timer; 3514 | 3515 | function next(i) { 3516 | var hook = hooks[i]; 3517 | if (!hook) return fn(); 3518 | self.currentRunnable = hook; 3519 | 3520 | self.emit('hook', hook); 3521 | 3522 | hook.on('error', function(err){ 3523 | self.failHook(hook, err); 3524 | }); 3525 | 3526 | hook.run(function(err){ 3527 | hook.removeAllListeners('error'); 3528 | var testError = hook.error(); 3529 | if (testError) self.fail(self.test, testError); 3530 | if (err) return self.failHook(hook, err); 3531 | self.emit('hook end', hook); 3532 | next(++i); 3533 | }); 3534 | } 3535 | 3536 | process.nextTick(function(){ 3537 | next(0); 3538 | }); 3539 | }; 3540 | 3541 | /** 3542 | * Run hook `name` for the given array of `suites` 3543 | * in order, and callback `fn(err)`. 3544 | * 3545 | * @param {String} name 3546 | * @param {Array} suites 3547 | * @param {Function} fn 3548 | * @api private 3549 | */ 3550 | 3551 | Runner.prototype.hooks = function(name, suites, fn){ 3552 | var self = this 3553 | , orig = this.suite; 3554 | 3555 | function next(suite) { 3556 | self.suite = suite; 3557 | 3558 | if (!suite) { 3559 | self.suite = orig; 3560 | return fn(); 3561 | } 3562 | 3563 | self.hook(name, function(err){ 3564 | if (err) { 3565 | self.suite = orig; 3566 | return fn(err); 3567 | } 3568 | 3569 | next(suites.pop()); 3570 | }); 3571 | } 3572 | 3573 | next(suites.pop()); 3574 | }; 3575 | 3576 | /** 3577 | * Run hooks from the top level down. 3578 | * 3579 | * @param {String} name 3580 | * @param {Function} fn 3581 | * @api private 3582 | */ 3583 | 3584 | Runner.prototype.hookUp = function(name, fn){ 3585 | var suites = [this.suite].concat(this.parents()).reverse(); 3586 | this.hooks(name, suites, fn); 3587 | }; 3588 | 3589 | /** 3590 | * Run hooks from the bottom up. 3591 | * 3592 | * @param {String} name 3593 | * @param {Function} fn 3594 | * @api private 3595 | */ 3596 | 3597 | Runner.prototype.hookDown = function(name, fn){ 3598 | var suites = [this.suite].concat(this.parents()); 3599 | this.hooks(name, suites, fn); 3600 | }; 3601 | 3602 | /** 3603 | * Return an array of parent Suites from 3604 | * closest to furthest. 3605 | * 3606 | * @return {Array} 3607 | * @api private 3608 | */ 3609 | 3610 | Runner.prototype.parents = function(){ 3611 | var suite = this.suite 3612 | , suites = []; 3613 | while (suite = suite.parent) suites.push(suite); 3614 | return suites; 3615 | }; 3616 | 3617 | /** 3618 | * Run the current test and callback `fn(err)`. 3619 | * 3620 | * @param {Function} fn 3621 | * @api private 3622 | */ 3623 | 3624 | Runner.prototype.runTest = function(fn){ 3625 | var test = this.test 3626 | , self = this; 3627 | 3628 | try { 3629 | test.on('error', function(err){ 3630 | self.fail(test, err); 3631 | }); 3632 | test.run(fn); 3633 | } catch (err) { 3634 | fn(err); 3635 | } 3636 | }; 3637 | 3638 | /** 3639 | * Run tests in the given `suite` and invoke 3640 | * the callback `fn()` when complete. 3641 | * 3642 | * @param {Suite} suite 3643 | * @param {Function} fn 3644 | * @api private 3645 | */ 3646 | 3647 | Runner.prototype.runTests = function(suite, fn){ 3648 | var self = this 3649 | , tests = suite.tests 3650 | , test; 3651 | 3652 | function next(err) { 3653 | // if we bail after first err 3654 | if (self.failures && suite._bail) return fn(); 3655 | 3656 | // next test 3657 | test = tests.shift(); 3658 | 3659 | // all done 3660 | if (!test) return fn(); 3661 | 3662 | // grep 3663 | var match = self._grep.test(test.fullTitle()); 3664 | if (self._invert) match = !match; 3665 | if (!match) return next(); 3666 | 3667 | // pending 3668 | if (test.pending) { 3669 | self.emit('pending', test); 3670 | self.emit('test end', test); 3671 | return next(); 3672 | } 3673 | 3674 | // execute test and hook(s) 3675 | self.emit('test', self.test = test); 3676 | self.hookDown('beforeEach', function(){ 3677 | self.currentRunnable = self.test; 3678 | self.runTest(function(err){ 3679 | test = self.test; 3680 | 3681 | if (err) { 3682 | self.fail(test, err); 3683 | self.emit('test end', test); 3684 | return self.hookUp('afterEach', next); 3685 | } 3686 | 3687 | test.state = 'passed'; 3688 | self.emit('pass', test); 3689 | self.emit('test end', test); 3690 | self.hookUp('afterEach', next); 3691 | }); 3692 | }); 3693 | } 3694 | 3695 | this.next = next; 3696 | next(); 3697 | }; 3698 | 3699 | /** 3700 | * Run the given `suite` and invoke the 3701 | * callback `fn()` when complete. 3702 | * 3703 | * @param {Suite} suite 3704 | * @param {Function} fn 3705 | * @api private 3706 | */ 3707 | 3708 | Runner.prototype.runSuite = function(suite, fn){ 3709 | var total = this.grepTotal(suite) 3710 | , self = this 3711 | , i = 0; 3712 | 3713 | debug('run suite %s', suite.fullTitle()); 3714 | 3715 | if (!total) return fn(); 3716 | 3717 | this.emit('suite', this.suite = suite); 3718 | 3719 | function next() { 3720 | var curr = suite.suites[i++]; 3721 | if (!curr) return done(); 3722 | self.runSuite(curr, next); 3723 | } 3724 | 3725 | function done() { 3726 | self.suite = suite; 3727 | self.hook('afterAll', function(){ 3728 | self.emit('suite end', suite); 3729 | fn(); 3730 | }); 3731 | } 3732 | 3733 | this.hook('beforeAll', function(){ 3734 | self.runTests(suite, next); 3735 | }); 3736 | }; 3737 | 3738 | /** 3739 | * Handle uncaught exceptions. 3740 | * 3741 | * @param {Error} err 3742 | * @api private 3743 | */ 3744 | 3745 | Runner.prototype.uncaught = function(err){ 3746 | debug('uncaught exception'); 3747 | var runnable = this.currentRunnable; 3748 | if ('failed' == runnable.state) return; 3749 | runnable.clearTimeout(); 3750 | err.uncaught = true; 3751 | this.fail(runnable, err); 3752 | 3753 | // recover from test 3754 | if ('test' == runnable.type) { 3755 | this.emit('test end', runnable); 3756 | this.hookUp('afterEach', this.next); 3757 | return; 3758 | } 3759 | 3760 | // bail on hooks 3761 | this.emit('end'); 3762 | }; 3763 | 3764 | /** 3765 | * Run the root suite and invoke `fn(failures)` 3766 | * on completion. 3767 | * 3768 | * @param {Function} fn 3769 | * @return {Runner} for chaining 3770 | * @api public 3771 | */ 3772 | 3773 | Runner.prototype.run = function(fn){ 3774 | var self = this 3775 | , fn = fn || function(){}; 3776 | 3777 | debug('start'); 3778 | 3779 | // uncaught callback 3780 | function uncaught(err) { 3781 | self.uncaught(err); 3782 | } 3783 | 3784 | // callback 3785 | this.on('end', function(){ 3786 | debug('end'); 3787 | process.removeListener('uncaughtException', uncaught); 3788 | fn(self.failures); 3789 | }); 3790 | 3791 | // run suites 3792 | this.emit('start'); 3793 | this.runSuite(this.suite, function(){ 3794 | debug('finished running'); 3795 | self.emit('end'); 3796 | }); 3797 | 3798 | // uncaught exception 3799 | process.on('uncaughtException', uncaught); 3800 | 3801 | return this; 3802 | }; 3803 | 3804 | /** 3805 | * Filter leaks with the given globals flagged as `ok`. 3806 | * 3807 | * @param {Array} ok 3808 | * @return {Array} 3809 | * @api private 3810 | */ 3811 | 3812 | function filterLeaks(ok) { 3813 | return filter(keys(global), function(key){ 3814 | var matched = filter(ok, function(ok){ 3815 | if (~ok.indexOf('*')) return 0 == key.indexOf(ok.split('*')[0]); 3816 | return key == ok; 3817 | }); 3818 | return matched.length == 0 && (!global.navigator || 'onerror' !== key); 3819 | }); 3820 | } 3821 | }); // module: runner.js 3822 | 3823 | require.register("suite.js", function(module, exports, require){ 3824 | 3825 | /** 3826 | * Module dependencies. 3827 | */ 3828 | 3829 | var EventEmitter = require('browser/events').EventEmitter 3830 | , debug = require('browser/debug')('suite') 3831 | , utils = require('./utils') 3832 | , Hook = require('./hook'); 3833 | 3834 | /** 3835 | * Expose `Suite`. 3836 | */ 3837 | 3838 | exports = module.exports = Suite; 3839 | 3840 | /** 3841 | * Create a new `Suite` with the given `title` 3842 | * and parent `Suite`. When a suite with the 3843 | * same title is already present, that suite 3844 | * is returned to provide nicer reporter 3845 | * and more flexible meta-testing. 3846 | * 3847 | * @param {Suite} parent 3848 | * @param {String} title 3849 | * @return {Suite} 3850 | * @api public 3851 | */ 3852 | 3853 | exports.create = function(parent, title){ 3854 | var suite = new Suite(title, parent.ctx); 3855 | suite.parent = parent; 3856 | title = suite.fullTitle(); 3857 | parent.addSuite(suite); 3858 | return suite; 3859 | }; 3860 | 3861 | /** 3862 | * Initialize a new `Suite` with the given 3863 | * `title` and `ctx`. 3864 | * 3865 | * @param {String} title 3866 | * @param {Context} ctx 3867 | * @api private 3868 | */ 3869 | 3870 | function Suite(title, ctx) { 3871 | this.title = title; 3872 | this.ctx = ctx; 3873 | this.suites = []; 3874 | this.tests = []; 3875 | this._beforeEach = []; 3876 | this._beforeAll = []; 3877 | this._afterEach = []; 3878 | this._afterAll = []; 3879 | this.root = !title; 3880 | this._timeout = 2000; 3881 | this._bail = false; 3882 | } 3883 | 3884 | /** 3885 | * Inherit from `EventEmitter.prototype`. 3886 | */ 3887 | 3888 | Suite.prototype = new EventEmitter; 3889 | Suite.prototype.constructor = Suite; 3890 | 3891 | 3892 | /** 3893 | * Return a clone of this `Suite`. 3894 | * 3895 | * @return {Suite} 3896 | * @api private 3897 | */ 3898 | 3899 | Suite.prototype.clone = function(){ 3900 | var suite = new Suite(this.title); 3901 | debug('clone'); 3902 | suite.ctx = this.ctx; 3903 | suite.timeout(this.timeout()); 3904 | suite.bail(this.bail()); 3905 | return suite; 3906 | }; 3907 | 3908 | /** 3909 | * Set timeout `ms` or short-hand such as "2s". 3910 | * 3911 | * @param {Number|String} ms 3912 | * @return {Suite|Number} for chaining 3913 | * @api private 3914 | */ 3915 | 3916 | Suite.prototype.timeout = function(ms){ 3917 | if (0 == arguments.length) return this._timeout; 3918 | if (String(ms).match(/s$/)) ms = parseFloat(ms) * 1000; 3919 | debug('timeout %d', ms); 3920 | this._timeout = parseInt(ms, 10); 3921 | return this; 3922 | }; 3923 | 3924 | /** 3925 | * Sets whether to bail after first error. 3926 | * 3927 | * @parma {Boolean} bail 3928 | * @return {Suite|Number} for chaining 3929 | * @api private 3930 | */ 3931 | 3932 | Suite.prototype.bail = function(bail){ 3933 | if (0 == arguments.length) return this._bail; 3934 | debug('bail %s', bail); 3935 | this._bail = bail; 3936 | return this; 3937 | }; 3938 | 3939 | /** 3940 | * Run `fn(test[, done])` before running tests. 3941 | * 3942 | * @param {Function} fn 3943 | * @return {Suite} for chaining 3944 | * @api private 3945 | */ 3946 | 3947 | Suite.prototype.beforeAll = function(fn){ 3948 | var hook = new Hook('"before all" hook', fn); 3949 | hook.parent = this; 3950 | hook.timeout(this.timeout()); 3951 | hook.ctx = this.ctx; 3952 | this._beforeAll.push(hook); 3953 | this.emit('beforeAll', hook); 3954 | return this; 3955 | }; 3956 | 3957 | /** 3958 | * Run `fn(test[, done])` after running tests. 3959 | * 3960 | * @param {Function} fn 3961 | * @return {Suite} for chaining 3962 | * @api private 3963 | */ 3964 | 3965 | Suite.prototype.afterAll = function(fn){ 3966 | var hook = new Hook('"after all" hook', fn); 3967 | hook.parent = this; 3968 | hook.timeout(this.timeout()); 3969 | hook.ctx = this.ctx; 3970 | this._afterAll.push(hook); 3971 | this.emit('afterAll', hook); 3972 | return this; 3973 | }; 3974 | 3975 | /** 3976 | * Run `fn(test[, done])` before each test case. 3977 | * 3978 | * @param {Function} fn 3979 | * @return {Suite} for chaining 3980 | * @api private 3981 | */ 3982 | 3983 | Suite.prototype.beforeEach = function(fn){ 3984 | var hook = new Hook('"before each" hook', fn); 3985 | hook.parent = this; 3986 | hook.timeout(this.timeout()); 3987 | hook.ctx = this.ctx; 3988 | this._beforeEach.push(hook); 3989 | this.emit('beforeEach', hook); 3990 | return this; 3991 | }; 3992 | 3993 | /** 3994 | * Run `fn(test[, done])` after each test case. 3995 | * 3996 | * @param {Function} fn 3997 | * @return {Suite} for chaining 3998 | * @api private 3999 | */ 4000 | 4001 | Suite.prototype.afterEach = function(fn){ 4002 | var hook = new Hook('"after each" hook', fn); 4003 | hook.parent = this; 4004 | hook.timeout(this.timeout()); 4005 | hook.ctx = this.ctx; 4006 | this._afterEach.push(hook); 4007 | this.emit('afterEach', hook); 4008 | return this; 4009 | }; 4010 | 4011 | /** 4012 | * Add a test `suite`. 4013 | * 4014 | * @param {Suite} suite 4015 | * @return {Suite} for chaining 4016 | * @api private 4017 | */ 4018 | 4019 | Suite.prototype.addSuite = function(suite){ 4020 | suite.parent = this; 4021 | suite.timeout(this.timeout()); 4022 | suite.bail(this.bail()); 4023 | this.suites.push(suite); 4024 | this.emit('suite', suite); 4025 | return this; 4026 | }; 4027 | 4028 | /** 4029 | * Add a `test` to this suite. 4030 | * 4031 | * @param {Test} test 4032 | * @return {Suite} for chaining 4033 | * @api private 4034 | */ 4035 | 4036 | Suite.prototype.addTest = function(test){ 4037 | test.parent = this; 4038 | test.timeout(this.timeout()); 4039 | test.ctx = this.ctx; 4040 | this.tests.push(test); 4041 | this.emit('test', test); 4042 | return this; 4043 | }; 4044 | 4045 | /** 4046 | * Return the full title generated by recursively 4047 | * concatenating the parent's full title. 4048 | * 4049 | * @return {String} 4050 | * @api public 4051 | */ 4052 | 4053 | Suite.prototype.fullTitle = function(){ 4054 | if (this.parent) { 4055 | var full = this.parent.fullTitle(); 4056 | if (full) return full + ' ' + this.title; 4057 | } 4058 | return this.title; 4059 | }; 4060 | 4061 | /** 4062 | * Return the total number of tests. 4063 | * 4064 | * @return {Number} 4065 | * @api public 4066 | */ 4067 | 4068 | Suite.prototype.total = function(){ 4069 | return utils.reduce(this.suites, function(sum, suite){ 4070 | return sum + suite.total(); 4071 | }, 0) + this.tests.length; 4072 | }; 4073 | 4074 | /** 4075 | * Iterates through each suite recursively to find 4076 | * all tests. Applies a function in the format 4077 | * `fn(test)`. 4078 | * 4079 | * @param {Function} fn 4080 | * @return {Suite} 4081 | * @api private 4082 | */ 4083 | 4084 | Suite.prototype.eachTest = function(fn){ 4085 | utils.forEach(this.tests, fn); 4086 | utils.forEach(this.suites, function(suite){ 4087 | suite.eachTest(fn); 4088 | }); 4089 | return this; 4090 | }; 4091 | 4092 | }); // module: suite.js 4093 | 4094 | require.register("test.js", function(module, exports, require){ 4095 | 4096 | /** 4097 | * Module dependencies. 4098 | */ 4099 | 4100 | var Runnable = require('./runnable'); 4101 | 4102 | /** 4103 | * Expose `Test`. 4104 | */ 4105 | 4106 | module.exports = Test; 4107 | 4108 | /** 4109 | * Initialize a new `Test` with the given `title` and callback `fn`. 4110 | * 4111 | * @param {String} title 4112 | * @param {Function} fn 4113 | * @api private 4114 | */ 4115 | 4116 | function Test(title, fn) { 4117 | Runnable.call(this, title, fn); 4118 | this.pending = !fn; 4119 | this.type = 'test'; 4120 | } 4121 | 4122 | /** 4123 | * Inherit from `Runnable.prototype`. 4124 | */ 4125 | 4126 | Test.prototype = new Runnable; 4127 | Test.prototype.constructor = Test; 4128 | 4129 | 4130 | }); // module: test.js 4131 | 4132 | require.register("utils.js", function(module, exports, require){ 4133 | 4134 | /** 4135 | * Module dependencies. 4136 | */ 4137 | 4138 | var fs = require('browser/fs') 4139 | , path = require('browser/path') 4140 | , join = path.join 4141 | , debug = require('browser/debug')('watch'); 4142 | 4143 | /** 4144 | * Ignored directories. 4145 | */ 4146 | 4147 | var ignore = ['node_modules', '.git']; 4148 | 4149 | /** 4150 | * Escape special characters in the given string of html. 4151 | * 4152 | * @param {String} html 4153 | * @return {String} 4154 | * @api private 4155 | */ 4156 | 4157 | exports.escape = function(html) { 4158 | return String(html) 4159 | .replace(/&/g, '&') 4160 | .replace(/"/g, '"') 4161 | .replace(//g, '>'); 4163 | }; 4164 | 4165 | /** 4166 | * Array#forEach (<=IE8) 4167 | * 4168 | * @param {Array} array 4169 | * @param {Function} fn 4170 | * @param {Object} scope 4171 | * @api private 4172 | */ 4173 | 4174 | exports.forEach = function(arr, fn, scope) { 4175 | for (var i = 0, l = arr.length; i < l; i++) 4176 | fn.call(scope, arr[i], i); 4177 | }; 4178 | 4179 | /** 4180 | * Array#indexOf (<=IE8) 4181 | * 4182 | * @parma {Array} arr 4183 | * @param {Object} obj to find index of 4184 | * @param {Number} start 4185 | * @api private 4186 | */ 4187 | 4188 | exports.indexOf = function (arr, obj, start) { 4189 | for (var i = start || 0, l = arr.length; i < l; i++) { 4190 | if (arr[i] === obj) 4191 | return i; 4192 | } 4193 | return -1; 4194 | }; 4195 | 4196 | /** 4197 | * Array#reduce (<=IE8) 4198 | * 4199 | * @param {Array} array 4200 | * @param {Function} fn 4201 | * @param {Object} initial value 4202 | * @param {Object} scope 4203 | * @api private 4204 | */ 4205 | 4206 | exports.reduce = function(arr, fn, val, scope) { 4207 | var rval = val; 4208 | 4209 | for (var i = 0, l = arr.length; i < l; i++) { 4210 | rval = fn.call(scope, rval, arr[i], i, arr); 4211 | } 4212 | 4213 | return rval; 4214 | }; 4215 | 4216 | /** 4217 | * Array#filter (<=IE8) 4218 | * 4219 | * @param {Array} array 4220 | * @param {Function} fn 4221 | * @param {Object} scope 4222 | * @api private 4223 | */ 4224 | 4225 | exports.filter = function(arr, fn, scope) { 4226 | var ret = []; 4227 | 4228 | for (var i = 0, l = arr.length; i < l; i++) { 4229 | var val = arr[i]; 4230 | if (fn.call(scope, val, i, arr)) 4231 | ret.push(val); 4232 | } 4233 | 4234 | return ret; 4235 | }; 4236 | 4237 | /** 4238 | * Object.keys (<=IE8) 4239 | * 4240 | * @param {Object} obj 4241 | * @return {Array} keys 4242 | * @api private 4243 | */ 4244 | 4245 | exports.keys = Object.keys || function(obj) { 4246 | var keys = [] 4247 | , has = Object.prototype.hasOwnProperty // for `window` on <=IE8 4248 | 4249 | for (var key in obj) { 4250 | if (has.call(obj, key)) { 4251 | keys.push(key); 4252 | } 4253 | } 4254 | 4255 | return keys; 4256 | }; 4257 | 4258 | /** 4259 | * Watch the given `files` for changes 4260 | * and invoke `fn(file)` on modification. 4261 | * 4262 | * @param {Array} files 4263 | * @param {Function} fn 4264 | * @api private 4265 | */ 4266 | 4267 | exports.watch = function(files, fn){ 4268 | var options = { interval: 100 }; 4269 | files.forEach(function(file){ 4270 | debug('file %s', file); 4271 | fs.watchFile(file, options, function(curr, prev){ 4272 | if (prev.mtime < curr.mtime) fn(file); 4273 | }); 4274 | }); 4275 | }; 4276 | 4277 | /** 4278 | * Ignored files. 4279 | */ 4280 | 4281 | function ignored(path){ 4282 | return !~ignore.indexOf(path); 4283 | } 4284 | 4285 | /** 4286 | * Lookup files in the given `dir`. 4287 | * 4288 | * @return {Array} 4289 | * @api private 4290 | */ 4291 | 4292 | exports.files = function(dir, ret){ 4293 | ret = ret || []; 4294 | 4295 | fs.readdirSync(dir) 4296 | .filter(ignored) 4297 | .forEach(function(path){ 4298 | path = join(dir, path); 4299 | if (fs.statSync(path).isDirectory()) { 4300 | exports.files(path, ret); 4301 | } else if (path.match(/\.(js|coffee)$/)) { 4302 | ret.push(path); 4303 | } 4304 | }); 4305 | 4306 | return ret; 4307 | }; 4308 | 4309 | /** 4310 | * Compute a slug from the given `str`. 4311 | * 4312 | * @param {String} str 4313 | * @return {String} 4314 | * @api private 4315 | */ 4316 | 4317 | exports.slug = function(str){ 4318 | return str 4319 | .toLowerCase() 4320 | .replace(/ +/g, '-') 4321 | .replace(/[^-\w]/g, ''); 4322 | }; 4323 | 4324 | /** 4325 | * Strip the function definition from `str`, 4326 | * and re-indent for pre whitespace. 4327 | */ 4328 | 4329 | exports.clean = function(str) { 4330 | str = str 4331 | .replace(/^function *\(.*\) *{/, '') 4332 | .replace(/\s+\}$/, ''); 4333 | 4334 | var spaces = str.match(/^\n?( *)/)[1].length 4335 | , re = new RegExp('^ {' + spaces + '}', 'gm'); 4336 | 4337 | str = str.replace(re, ''); 4338 | 4339 | return str.trim(); 4340 | }; 4341 | 4342 | /** 4343 | * Escape regular expression characters in `str`. 4344 | * 4345 | * @param {String} str 4346 | * @return {String} 4347 | * @api private 4348 | */ 4349 | 4350 | exports.escapeRegexp = function(str){ 4351 | return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); 4352 | }; 4353 | }); // module: utils.js 4354 | /** 4355 | * Node shims. 4356 | * 4357 | * These are meant only to allow 4358 | * mocha.js to run untouched, not 4359 | * to allow running node code in 4360 | * the browser. 4361 | */ 4362 | 4363 | process = {}; 4364 | process.exit = function(status){}; 4365 | process.stdout = {}; 4366 | global = window; 4367 | 4368 | /** 4369 | * next tick implementation. 4370 | */ 4371 | 4372 | process.nextTick = (function(){ 4373 | // postMessage behaves badly on IE8 4374 | if (window.ActiveXObject || !window.postMessage) { 4375 | return function(fn){ fn() }; 4376 | } 4377 | 4378 | // based on setZeroTimeout by David Baron 4379 | // - http://dbaron.org/log/20100309-faster-timeouts 4380 | var timeouts = [] 4381 | , name = 'mocha-zero-timeout' 4382 | 4383 | window.addEventListener('message', function(e){ 4384 | if (e.source == window && e.data == name) { 4385 | if (e.stopPropagation) e.stopPropagation(); 4386 | if (timeouts.length) timeouts.shift()(); 4387 | } 4388 | }, true); 4389 | 4390 | return function(fn){ 4391 | timeouts.push(fn); 4392 | window.postMessage(name, '*'); 4393 | } 4394 | })(); 4395 | 4396 | /** 4397 | * Remove uncaughtException listener. 4398 | */ 4399 | 4400 | process.removeListener = function(e){ 4401 | if ('uncaughtException' == e) { 4402 | window.onerror = null; 4403 | } 4404 | }; 4405 | 4406 | /** 4407 | * Implements uncaughtException listener. 4408 | */ 4409 | 4410 | process.on = function(e, fn){ 4411 | if ('uncaughtException' == e) { 4412 | window.onerror = fn; 4413 | } 4414 | }; 4415 | 4416 | /** 4417 | * Expose mocha. 4418 | */ 4419 | 4420 | window.mocha = require('mocha'); 4421 | 4422 | // boot 4423 | ;(function(){ 4424 | var utils = mocha.utils 4425 | , options = {} 4426 | 4427 | mocha.suite = new mocha.Suite('', new mocha.Context()); 4428 | 4429 | /** 4430 | * Highlight the given string of `js`. 4431 | */ 4432 | 4433 | function highlight(js) { 4434 | return js 4435 | .replace(//g, '>') 4437 | .replace(/\/\/(.*)/gm, '//$1') 4438 | .replace(/('.*?')/gm, '$1') 4439 | .replace(/(\d+\.\d+)/gm, '$1') 4440 | .replace(/(\d+)/gm, '$1') 4441 | .replace(/\bnew *(\w+)/gm, 'new $1') 4442 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1') 4443 | } 4444 | 4445 | /** 4446 | * Highlight code contents. 4447 | */ 4448 | 4449 | function highlightCode() { 4450 | var code = document.getElementsByTagName('code'); 4451 | for (var i = 0, len = code.length; i < len; ++i) { 4452 | code[i].innerHTML = highlight(code[i].innerHTML); 4453 | } 4454 | } 4455 | 4456 | /** 4457 | * Parse the given `qs`. 4458 | */ 4459 | 4460 | function parse(qs) { 4461 | return utils.reduce(qs.replace('?', '').split('&'), function(obj, pair){ 4462 | var i = pair.indexOf('=') 4463 | , key = pair.slice(0, i) 4464 | , val = pair.slice(++i); 4465 | 4466 | obj[key] = decodeURIComponent(val); 4467 | return obj; 4468 | }, {}); 4469 | } 4470 | 4471 | /** 4472 | * Setup mocha with the given setting options. 4473 | */ 4474 | 4475 | mocha.setup = function(opts){ 4476 | if ('string' === typeof opts) options.ui = opts; 4477 | else options = opts; 4478 | 4479 | ui = mocha.interfaces[options.ui]; 4480 | if (!ui) throw new Error('invalid mocha interface "' + ui + '"'); 4481 | if (options.timeout) mocha.suite.timeout(options.timeout); 4482 | ui(mocha.suite); 4483 | mocha.suite.emit('pre-require', window); 4484 | }; 4485 | 4486 | /** 4487 | * Run mocha, returning the Runner. 4488 | */ 4489 | 4490 | mocha.run = function(fn){ 4491 | mocha.suite.emit('run'); 4492 | var runner = new mocha.Runner(mocha.suite); 4493 | var Reporter = options.reporter || mocha.reporters.HTML; 4494 | var reporter = new Reporter(runner); 4495 | var query = parse(window.location.search || ""); 4496 | if (query.grep) runner.grep(new RegExp(query.grep)); 4497 | if (options.ignoreLeaks) runner.ignoreLeaks = true; 4498 | if (options.globals) runner.globals(options.globals); 4499 | runner.globals(['location']); 4500 | runner.on('end', highlightCode); 4501 | return runner.run(fn); 4502 | }; 4503 | })(); 4504 | })(); --------------------------------------------------------------------------------