[Pkg-javascript-commits] [node-tap-mocha-reporter] 01/137: mocha's reporters
Bastien Roucariès
rouca at moszumanska.debian.org
Thu Sep 7 09:49:21 UTC 2017
This is an automated email from the git hooks/post-receive script.
rouca pushed a commit to branch master
in repository node-tap-mocha-reporter.
commit cac8c3759de141f21e1e5c2dabae855cbe582f21
Author: isaacs <i at izs.me>
Date: Sun Mar 29 07:27:38 2015 -1000
mocha's reporters
---
lib/reporters/base.js | 459 ++++++++++++++++++++++++++++++++++
lib/reporters/doc.js | 62 +++++
lib/reporters/dot.js | 62 +++++
lib/reporters/html-cov.js | 50 ++++
lib/reporters/html.js | 287 +++++++++++++++++++++
lib/reporters/index.js | 17 ++
lib/reporters/json-cov.js | 152 +++++++++++
lib/reporters/json-stream.js | 62 +++++
lib/reporters/json.js | 92 +++++++
lib/reporters/landing.js | 96 +++++++
lib/reporters/list.js | 63 +++++
lib/reporters/markdown.js | 100 ++++++++
lib/reporters/min.js | 37 +++
lib/reporters/nyan.js | 260 +++++++++++++++++++
lib/reporters/progress.js | 92 +++++++
lib/reporters/spec.js | 82 ++++++
lib/reporters/tap.js | 72 ++++++
lib/reporters/templates/coverage.jade | 51 ++++
lib/reporters/templates/menu.jade | 13 +
lib/reporters/templates/script.html | 34 +++
lib/reporters/templates/style.html | 324 ++++++++++++++++++++++++
lib/reporters/xunit.js | 149 +++++++++++
22 files changed, 2616 insertions(+)
diff --git a/lib/reporters/base.js b/lib/reporters/base.js
new file mode 100644
index 0000000..c253c11
--- /dev/null
+++ b/lib/reporters/base.js
@@ -0,0 +1,459 @@
+/**
+ * Module dependencies.
+ */
+
+var tty = require('tty')
+ , diff = require('diff')
+ , ms = require('../ms')
+ , utils = require('../utils')
+ , supportsColor = process.env ? require('supports-color') : null;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Check if both stdio streams are associated with a tty.
+ */
+
+var isatty = tty.isatty(1) && tty.isatty(2);
+
+/**
+ * Expose `Base`.
+ */
+
+exports = module.exports = Base;
+
+/**
+ * Enable coloring by default, except in the browser interface.
+ */
+
+exports.useColors = process.env
+ ? (supportsColor || (process.env.MOCHA_COLORS !== undefined))
+ : false;
+
+/**
+ * Inline diffs instead of +/-
+ */
+
+exports.inlineDiffs = false;
+
+/**
+ * Default color map.
+ */
+
+exports.colors = {
+ 'pass': 90
+ , 'fail': 31
+ , 'bright pass': 92
+ , 'bright fail': 91
+ , 'bright yellow': 93
+ , 'pending': 36
+ , 'suite': 0
+ , 'error title': 0
+ , 'error message': 31
+ , 'error stack': 90
+ , 'checkmark': 32
+ , 'fast': 90
+ , 'medium': 33
+ , 'slow': 31
+ , 'green': 32
+ , 'light': 90
+ , 'diff gutter': 90
+ , 'diff added': 42
+ , 'diff removed': 41
+};
+
+/**
+ * Default symbol map.
+ */
+
+exports.symbols = {
+ ok: '✓',
+ err: '✖',
+ dot: '․'
+};
+
+// With node.js on Windows: use symbols available in terminal default fonts
+if ('win32' == process.platform) {
+ exports.symbols.ok = '\u221A';
+ exports.symbols.err = '\u00D7';
+ exports.symbols.dot = '.';
+}
+
+/**
+ * Color `str` with the given `type`,
+ * allowing colors to be disabled,
+ * as well as user-defined color
+ * schemes.
+ *
+ * @param {String} type
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+var color = exports.color = function(type, str) {
+ if (!exports.useColors) return String(str);
+ return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m';
+};
+
+/**
+ * Expose term window size, with some
+ * defaults for when stderr is not a tty.
+ */
+
+exports.window = {
+ width: isatty
+ ? process.stdout.getWindowSize
+ ? process.stdout.getWindowSize(1)[0]
+ : tty.getWindowSize()[1]
+ : 75
+};
+
+/**
+ * Expose some basic cursor interactions
+ * that are common among reporters.
+ */
+
+exports.cursor = {
+ hide: function(){
+ isatty && process.stdout.write('\u001b[?25l');
+ },
+
+ show: function(){
+ isatty && process.stdout.write('\u001b[?25h');
+ },
+
+ deleteLine: function(){
+ isatty && process.stdout.write('\u001b[2K');
+ },
+
+ beginningOfLine: function(){
+ isatty && process.stdout.write('\u001b[0G');
+ },
+
+ CR: function(){
+ if (isatty) {
+ exports.cursor.deleteLine();
+ exports.cursor.beginningOfLine();
+ } else {
+ process.stdout.write('\r');
+ }
+ }
+};
+
+/**
+ * Outut the given `failures` as a list.
+ *
+ * @param {Array} failures
+ * @api public
+ */
+
+exports.list = function(failures){
+ console.log();
+ failures.forEach(function(test, i){
+ // format
+ var fmt = color('error title', ' %s) %s:\n')
+ + color('error message', ' %s')
+ + color('error stack', '\n%s\n');
+
+ // msg
+ var err = test.err
+ , message = err.message || ''
+ , stack = err.stack || message
+ , index = stack.indexOf(message) + message.length
+ , msg = stack.slice(0, index)
+ , actual = err.actual
+ , expected = err.expected
+ , escape = true;
+
+ // uncaught
+ if (err.uncaught) {
+ msg = 'Uncaught ' + msg;
+ }
+ // explicitly show diff
+ if (err.showDiff && sameType(actual, expected)) {
+
+ if ('string' !== typeof actual) {
+ escape = false;
+ err.actual = actual = utils.stringify(actual);
+ err.expected = expected = utils.stringify(expected);
+ }
+
+ fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n');
+ var match = message.match(/^([^:]+): expected/);
+ msg = '\n ' + color('error message', match ? match[1] : msg);
+
+ if (exports.inlineDiffs) {
+ msg += inlineDiff(err, escape);
+ } else {
+ msg += unifiedDiff(err, escape);
+ }
+ }
+
+ // indent stack trace without msg
+ stack = stack.slice(index ? index + 1 : index)
+ .replace(/^/gm, ' ');
+
+ console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
+ });
+};
+
+/**
+ * Initialize a new `Base` reporter.
+ *
+ * All other reporters generally
+ * inherit from this reporter, providing
+ * stats such as test duration, number
+ * of tests passed / failed etc.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Base(runner) {
+ var self = this
+ , stats = this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }
+ , failures = this.failures = [];
+
+ if (!runner) return;
+ this.runner = runner;
+
+ runner.stats = stats;
+
+ runner.on('start', function(){
+ stats.start = new Date;
+ });
+
+ runner.on('suite', function(suite){
+ stats.suites = stats.suites || 0;
+ suite.root || stats.suites++;
+ });
+
+ runner.on('test end', function(test){
+ stats.tests = stats.tests || 0;
+ stats.tests++;
+ });
+
+ runner.on('pass', function(test){
+ stats.passes = stats.passes || 0;
+
+ var medium = test.slow() / 2;
+ test.speed = test.duration > test.slow()
+ ? 'slow'
+ : test.duration > medium
+ ? 'medium'
+ : 'fast';
+
+ stats.passes++;
+ });
+
+ runner.on('fail', function(test, err){
+ stats.failures = stats.failures || 0;
+ stats.failures++;
+ test.err = err;
+ failures.push(test);
+ });
+
+ runner.on('end', function(){
+ stats.end = new Date;
+ stats.duration = new Date - stats.start;
+ });
+
+ runner.on('pending', function(){
+ stats.pending++;
+ });
+}
+
+/**
+ * Output common epilogue used by many of
+ * the bundled reporters.
+ *
+ * @api public
+ */
+
+Base.prototype.epilogue = function(){
+ var stats = this.stats;
+ var tests;
+ var fmt;
+
+ console.log();
+
+ // passes
+ fmt = color('bright pass', ' ')
+ + color('green', ' %d passing')
+ + color('light', ' (%s)');
+
+ console.log(fmt,
+ stats.passes || 0,
+ ms(stats.duration));
+
+ // pending
+ if (stats.pending) {
+ fmt = color('pending', ' ')
+ + color('pending', ' %d pending');
+
+ console.log(fmt, stats.pending);
+ }
+
+ // failures
+ if (stats.failures) {
+ fmt = color('fail', ' %d failing');
+
+ console.log(fmt, stats.failures);
+
+ Base.list(this.failures);
+ console.log();
+ }
+
+ console.log();
+};
+
+/**
+ * Pad the given `str` to `len`.
+ *
+ * @param {String} str
+ * @param {String} len
+ * @return {String}
+ * @api private
+ */
+
+function pad(str, len) {
+ str = String(str);
+ return Array(len - str.length + 1).join(' ') + str;
+}
+
+
+/**
+ * Returns an inline diff between 2 strings with coloured ANSI output
+ *
+ * @param {Error} Error with actual/expected
+ * @return {String} Diff
+ * @api private
+ */
+
+function inlineDiff(err, escape) {
+ var msg = errorDiff(err, 'WordsWithSpace', escape);
+
+ // linenos
+ var lines = msg.split('\n');
+ if (lines.length > 4) {
+ var width = String(lines.length).length;
+ msg = lines.map(function(str, i){
+ return pad(++i, width) + ' |' + ' ' + str;
+ }).join('\n');
+ }
+
+ // legend
+ msg = '\n'
+ + color('diff removed', 'actual')
+ + ' '
+ + color('diff added', 'expected')
+ + '\n\n'
+ + msg
+ + '\n';
+
+ // indent
+ msg = msg.replace(/^/gm, ' ');
+ return msg;
+}
+
+/**
+ * Returns a unified diff between 2 strings
+ *
+ * @param {Error} Error with actual/expected
+ * @return {String} Diff
+ * @api private
+ */
+
+function unifiedDiff(err, escape) {
+ var indent = ' ';
+ function cleanUp(line) {
+ if (escape) {
+ line = escapeInvisibles(line);
+ }
+ if (line[0] === '+') return indent + colorLines('diff added', line);
+ if (line[0] === '-') return indent + colorLines('diff removed', line);
+ if (line.match(/\@\@/)) return null;
+ if (line.match(/\\ No newline/)) return null;
+ else return indent + line;
+ }
+ function notBlank(line) {
+ return line != null;
+ }
+ var msg = diff.createPatch('string', err.actual, err.expected);
+ var lines = msg.split('\n').splice(4);
+ return '\n '
+ + colorLines('diff added', '+ expected') + ' '
+ + colorLines('diff removed', '- actual')
+ + '\n\n'
+ + lines.map(cleanUp).filter(notBlank).join('\n');
+}
+
+/**
+ * Return a character diff for `err`.
+ *
+ * @param {Error} err
+ * @return {String}
+ * @api private
+ */
+
+function errorDiff(err, type, escape) {
+ var actual = escape ? escapeInvisibles(err.actual) : err.actual;
+ var expected = escape ? escapeInvisibles(err.expected) : err.expected;
+ return diff['diff' + type](actual, expected).map(function(str){
+ if (str.added) return colorLines('diff added', str.value);
+ if (str.removed) return colorLines('diff removed', str.value);
+ return str.value;
+ }).join('');
+}
+
+/**
+ * Returns a string with all invisible characters in plain text
+ *
+ * @param {String} line
+ * @return {String}
+ * @api private
+ */
+function escapeInvisibles(line) {
+ return line.replace(/\t/g, '<tab>')
+ .replace(/\r/g, '<CR>')
+ .replace(/\n/g, '<LF>\n');
+}
+
+/**
+ * Color lines for `str`, using the color `name`.
+ *
+ * @param {String} name
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+function colorLines(name, str) {
+ return str.split('\n').map(function(str){
+ return color(name, str);
+ }).join('\n');
+}
+
+/**
+ * Check that a / b have the same type.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Boolean}
+ * @api private
+ */
+
+function sameType(a, b) {
+ a = Object.prototype.toString.call(a);
+ b = Object.prototype.toString.call(b);
+ return a == b;
+}
diff --git a/lib/reporters/doc.js b/lib/reporters/doc.js
new file mode 100644
index 0000000..d194eb0
--- /dev/null
+++ b/lib/reporters/doc.js
@@ -0,0 +1,62 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils');
+
+/**
+ * Expose `Doc`.
+ */
+
+exports = module.exports = Doc;
+
+/**
+ * Initialize a new `Doc` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Doc(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , total = runner.total
+ , indents = 2;
+
+ function indent() {
+ return Array(indents).join(' ');
+ }
+
+ runner.on('suite', function(suite){
+ if (suite.root) return;
+ ++indents;
+ console.log('%s<section class="suite">', indent());
+ ++indents;
+ console.log('%s<h1>%s</h1>', indent(), utils.escape(suite.title));
+ console.log('%s<dl>', indent());
+ });
+
+ runner.on('suite end', function(suite){
+ if (suite.root) return;
+ console.log('%s</dl>', indent());
+ --indents;
+ console.log('%s</section>', indent());
+ --indents;
+ });
+
+ runner.on('pass', function(test){
+ console.log('%s <dt>%s</dt>', indent(), utils.escape(test.title));
+ var code = utils.escape(utils.clean(test.fn.toString()));
+ console.log('%s <dd><pre><code>%s</code></pre></dd>', indent(), code);
+ });
+
+ runner.on('fail', function(test, err){
+ console.log('%s <dt class="error">%s</dt>', indent(), utils.escape(test.title));
+ var code = utils.escape(utils.clean(test.fn.toString()));
+ console.log('%s <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code);
+ console.log('%s <dd class="error">%s</dd>', indent(), utils.escape(err));
+ });
+}
diff --git a/lib/reporters/dot.js b/lib/reporters/dot.js
new file mode 100644
index 0000000..42a45ee
--- /dev/null
+++ b/lib/reporters/dot.js
@@ -0,0 +1,62 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , color = Base.color;
+
+/**
+ * Expose `Dot`.
+ */
+
+exports = module.exports = Dot;
+
+/**
+ * Initialize a new `Dot` matrix test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Dot(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .75 | 0
+ , n = -1;
+
+ runner.on('start', function(){
+ process.stdout.write('\n');
+ });
+
+ runner.on('pending', function(test){
+ if (++n % width == 0) process.stdout.write('\n ');
+ process.stdout.write(color('pending', Base.symbols.dot));
+ });
+
+ runner.on('pass', function(test){
+ if (++n % width == 0) process.stdout.write('\n ');
+ if ('slow' == test.speed) {
+ process.stdout.write(color('bright yellow', Base.symbols.dot));
+ } else {
+ process.stdout.write(color(test.speed, Base.symbols.dot));
+ }
+ });
+
+ runner.on('fail', function(test, err){
+ if (++n % width == 0) process.stdout.write('\n ');
+ process.stdout.write(color('fail', Base.symbols.dot));
+ });
+
+ runner.on('end', function(){
+ console.log();
+ self.epilogue();
+ });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+Dot.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/html-cov.js b/lib/reporters/html-cov.js
new file mode 100644
index 0000000..74b46ad
--- /dev/null
+++ b/lib/reporters/html-cov.js
@@ -0,0 +1,50 @@
+/**
+ * Module dependencies.
+ */
+
+var JSONCov = require('./json-cov')
+ , fs = require('fs');
+
+/**
+ * Expose `HTMLCov`.
+ */
+
+exports = module.exports = HTMLCov;
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function HTMLCov(runner) {
+ var jade = require('jade')
+ , file = __dirname + '/templates/coverage.jade'
+ , str = fs.readFileSync(file, 'utf8')
+ , fn = jade.compile(str, { filename: file })
+ , self = this;
+
+ JSONCov.call(this, runner, false);
+
+ runner.on('end', function(){
+ process.stdout.write(fn({
+ cov: self.cov
+ , coverageClass: coverageClass
+ }));
+ });
+}
+
+/**
+ * Return coverage class for `n`.
+ *
+ * @return {String}
+ * @api private
+ */
+
+function coverageClass(n) {
+ if (n >= 75) return 'high';
+ if (n >= 50) return 'medium';
+ if (n >= 25) return 'low';
+ return 'terrible';
+}
diff --git a/lib/reporters/html.js b/lib/reporters/html.js
new file mode 100644
index 0000000..aec2af0
--- /dev/null
+++ b/lib/reporters/html.js
@@ -0,0 +1,287 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils')
+ , Progress = require('../browser/progress')
+ , escape = utils.escape;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Expose `HTML`.
+ */
+
+exports = module.exports = HTML;
+
+/**
+ * Stats template.
+ */
+
+var statsTemplate = '<ul id="mocha-stats">'
+ + '<li class="progress"><canvas width="40" height="40"></canvas></li>'
+ + '<li class="passes"><a href="#">passes:</a> <em>0</em></li>'
+ + '<li class="failures"><a href="#">failures:</a> <em>0</em></li>'
+ + '<li class="duration">duration: <em>0</em>s</li>'
+ + '</ul>';
+
+/**
+ * Initialize a new `HTML` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function HTML(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , total = runner.total
+ , stat = fragment(statsTemplate)
+ , items = stat.getElementsByTagName('li')
+ , passes = items[1].getElementsByTagName('em')[0]
+ , passesLink = items[1].getElementsByTagName('a')[0]
+ , failures = items[2].getElementsByTagName('em')[0]
+ , failuresLink = items[2].getElementsByTagName('a')[0]
+ , duration = items[3].getElementsByTagName('em')[0]
+ , canvas = stat.getElementsByTagName('canvas')[0]
+ , report = fragment('<ul id="mocha-report"></ul>')
+ , stack = [report]
+ , progress
+ , ctx
+ , root = document.getElementById('mocha');
+
+ if (canvas.getContext) {
+ var ratio = window.devicePixelRatio || 1;
+ canvas.style.width = canvas.width;
+ canvas.style.height = canvas.height;
+ canvas.width *= ratio;
+ canvas.height *= ratio;
+ ctx = canvas.getContext('2d');
+ ctx.scale(ratio, ratio);
+ progress = new Progress;
+ }
+
+ if (!root) return error('#mocha div missing, add it to your document');
+
+ // pass toggle
+ on(passesLink, 'click', function(){
+ unhide();
+ var name = /pass/.test(report.className) ? '' : ' pass';
+ report.className = report.className.replace(/fail|pass/g, '') + name;
+ if (report.className.trim()) hideSuitesWithout('test pass');
+ });
+
+ // failure toggle
+ on(failuresLink, 'click', function(){
+ unhide();
+ var name = /fail/.test(report.className) ? '' : ' fail';
+ report.className = report.className.replace(/fail|pass/g, '') + name;
+ if (report.className.trim()) hideSuitesWithout('test fail');
+ });
+
+ root.appendChild(stat);
+ root.appendChild(report);
+
+ if (progress) progress.size(40);
+
+ runner.on('suite', function(suite){
+ if (suite.root) return;
+
+ // suite
+ var url = self.suiteURL(suite);
+ var el = fragment('<li class="suite"><h1><a href="%s">%s</a></h1></li>', url, escape(suite.title));
+
+ // container
+ stack[0].appendChild(el);
+ stack.unshift(document.createElement('ul'));
+ el.appendChild(stack[0]);
+ });
+
+ runner.on('suite end', function(suite){
+ if (suite.root) return;
+ stack.shift();
+ });
+
+ runner.on('fail', function(test, err){
+ if ('hook' == test.type) runner.emit('test end', test);
+ });
+
+ runner.on('test end', function(test){
+ // TODO: add to stats
+ var percent = stats.tests / this.total * 100 | 0;
+ if (progress) progress.update(percent).draw(ctx);
+
+ // update stats
+ var ms = new Date - stats.start;
+ text(passes, stats.passes);
+ text(failures, stats.failures);
+ text(duration, (ms / 1000).toFixed(2));
+
+ // test
+ if ('passed' == test.state) {
+ var url = self.testURL(test);
+ var el = fragment('<li class="test pass %e"><h2>%e<span class="duration">%ems</span> <a href="%s" class="replay">‣</a></h2></li>', test.speed, test.title, test.duration, url);
+ } else if (test.pending) {
+ var el = fragment('<li class="test pass pending"><h2>%e</h2></li>', test.title);
+ } else {
+ var el = fragment('<li class="test fail"><h2>%e <a href="%e" class="replay">‣</a></h2></li>', test.title, self.testURL(test));
+ var str = test.err.stack || test.err.toString();
+
+ // FF / Opera do not add the message
+ if (!~str.indexOf(test.err.message)) {
+ str = test.err.message + '\n' + str;
+ }
+
+ // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we
+ // check for the result of the stringifying.
+ if ('[object Error]' == str) str = test.err.message;
+
+ // Safari doesn't give you a stack. Let's at least provide a source line.
+ if (!test.err.stack && test.err.sourceURL && test.err.line !== undefined) {
+ str += "\n(" + test.err.sourceURL + ":" + test.err.line + ")";
+ }
+
+ el.appendChild(fragment('<pre class="error">%e</pre>', str));
+ }
+
+ // toggle code
+ // TODO: defer
+ if (!test.pending) {
+ var h2 = el.getElementsByTagName('h2')[0];
+
+ on(h2, 'click', function(){
+ pre.style.display = 'none' == pre.style.display
+ ? 'block'
+ : 'none';
+ });
+
+ var pre = fragment('<pre><code>%e</code></pre>', utils.clean(test.fn.toString()));
+ el.appendChild(pre);
+ pre.style.display = 'none';
+ }
+
+ // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
+ if (stack[0]) stack[0].appendChild(el);
+ });
+}
+
+/**
+ * Makes a URL, preserving querystring ("search") parameters.
+ * @param {string} s
+ * @returns {string} your new URL
+ */
+var makeUrl = function makeUrl(s) {
+ var search = window.location.search;
+
+ // Remove previous grep query parameter if present
+ if (search) {
+ search = search.replace(/[?&]grep=[^&\s]*/g, '').replace(/^&/, '?');
+ }
+
+ return window.location.pathname + (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
+};
+
+/**
+ * Provide suite URL
+ *
+ * @param {Object} [suite]
+ */
+HTML.prototype.suiteURL = function(suite){
+ return makeUrl(suite.fullTitle());
+};
+
+/**
+ * Provide test URL
+ *
+ * @param {Object} [test]
+ */
+
+HTML.prototype.testURL = function(test){
+ return makeUrl(test.fullTitle());
+};
+
+/**
+ * Display error `msg`.
+ */
+
+function error(msg) {
+ document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
+}
+
+/**
+ * Return a DOM fragment from `html`.
+ */
+
+function fragment(html) {
+ var args = arguments
+ , div = document.createElement('div')
+ , i = 1;
+
+ div.innerHTML = html.replace(/%([se])/g, function(_, type){
+ switch (type) {
+ case 's': return String(args[i++]);
+ case 'e': return escape(args[i++]);
+ }
+ });
+
+ return div.firstChild;
+}
+
+/**
+ * Check for suites that do not have elements
+ * with `classname`, and hide them.
+ */
+
+function hideSuitesWithout(classname) {
+ var suites = document.getElementsByClassName('suite');
+ for (var i = 0; i < suites.length; i++) {
+ var els = suites[i].getElementsByClassName(classname);
+ if (0 == els.length) suites[i].className += ' hidden';
+ }
+}
+
+/**
+ * Unhide .hidden suites.
+ */
+
+function unhide() {
+ var els = document.getElementsByClassName('suite hidden');
+ for (var i = 0; i < els.length; ++i) {
+ els[i].className = els[i].className.replace('suite hidden', 'suite');
+ }
+}
+
+/**
+ * Set `el` text to `str`.
+ */
+
+function text(el, str) {
+ if (el.textContent) {
+ el.textContent = str;
+ } else {
+ el.innerText = str;
+ }
+}
+
+/**
+ * Listen on `event` with callback `fn`.
+ */
+
+function on(el, event, fn) {
+ if (el.addEventListener) {
+ el.addEventListener(event, fn, false);
+ } else {
+ el.attachEvent('on' + event, fn);
+ }
+}
diff --git a/lib/reporters/index.js b/lib/reporters/index.js
new file mode 100644
index 0000000..87b76d9
--- /dev/null
+++ b/lib/reporters/index.js
@@ -0,0 +1,17 @@
+exports.Base = require('./base');
+exports.Dot = require('./dot');
+exports.Doc = require('./doc');
+exports.TAP = require('./tap');
+exports.JSON = require('./json');
+exports.HTML = require('./html');
+exports.List = require('./list');
+exports.Min = require('./min');
+exports.Spec = require('./spec');
+exports.Nyan = require('./nyan');
+exports.XUnit = require('./xunit');
+exports.Markdown = require('./markdown');
+exports.Progress = require('./progress');
+exports.Landing = require('./landing');
+exports.JSONCov = require('./json-cov');
+exports.HTMLCov = require('./html-cov');
+exports.JSONStream = require('./json-stream');
diff --git a/lib/reporters/json-cov.js b/lib/reporters/json-cov.js
new file mode 100644
index 0000000..309c0ef
--- /dev/null
+++ b/lib/reporters/json-cov.js
@@ -0,0 +1,152 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `JSONCov`.
+ */
+
+exports = module.exports = JSONCov;
+
+/**
+ * Initialize a new `JsCoverage` reporter.
+ *
+ * @param {Runner} runner
+ * @param {Boolean} output
+ * @api public
+ */
+
+function JSONCov(runner, output) {
+ var self = this
+ , output = 1 == arguments.length ? true : output;
+
+ Base.call(this, runner);
+
+ var tests = []
+ , failures = []
+ , passes = [];
+
+ runner.on('test end', function(test){
+ tests.push(test);
+ });
+
+ runner.on('pass', function(test){
+ passes.push(test);
+ });
+
+ runner.on('fail', function(test){
+ failures.push(test);
+ });
+
+ runner.on('end', function(){
+ var cov = global._$jscoverage || {};
+ var result = self.cov = map(cov);
+ result.stats = self.stats;
+ result.tests = tests.map(clean);
+ result.failures = failures.map(clean);
+ result.passes = passes.map(clean);
+ if (!output) return;
+ process.stdout.write(JSON.stringify(result, null, 2 ));
+ });
+}
+
+/**
+ * Map jscoverage data to a JSON structure
+ * suitable for reporting.
+ *
+ * @param {Object} cov
+ * @return {Object}
+ * @api private
+ */
+
+function map(cov) {
+ var ret = {
+ instrumentation: 'node-jscoverage'
+ , sloc: 0
+ , hits: 0
+ , misses: 0
+ , coverage: 0
+ , files: []
+ };
+
+ for (var filename in cov) {
+ var data = coverage(filename, cov[filename]);
+ ret.files.push(data);
+ ret.hits += data.hits;
+ ret.misses += data.misses;
+ ret.sloc += data.sloc;
+ }
+
+ ret.files.sort(function(a, b) {
+ return a.filename.localeCompare(b.filename);
+ });
+
+ if (ret.sloc > 0) {
+ ret.coverage = (ret.hits / ret.sloc) * 100;
+ }
+
+ return ret;
+}
+
+/**
+ * Map jscoverage data for a single source file
+ * to a JSON structure suitable for reporting.
+ *
+ * @param {String} filename name of the source file
+ * @param {Object} data jscoverage coverage data
+ * @return {Object}
+ * @api private
+ */
+
+function coverage(filename, data) {
+ var ret = {
+ filename: filename,
+ coverage: 0,
+ hits: 0,
+ misses: 0,
+ sloc: 0,
+ source: {}
+ };
+
+ data.source.forEach(function(line, num){
+ num++;
+
+ if (data[num] === 0) {
+ ret.misses++;
+ ret.sloc++;
+ } else if (data[num] !== undefined) {
+ ret.hits++;
+ ret.sloc++;
+ }
+
+ ret.source[num] = {
+ source: line
+ , coverage: data[num] === undefined
+ ? ''
+ : data[num]
+ };
+ });
+
+ ret.coverage = ret.hits / ret.sloc * 100;
+
+ return ret;
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+ return {
+ title: test.title
+ , fullTitle: test.fullTitle()
+ , duration: test.duration
+ }
+}
diff --git a/lib/reporters/json-stream.js b/lib/reporters/json-stream.js
new file mode 100644
index 0000000..f7c05a8
--- /dev/null
+++ b/lib/reporters/json-stream.js
@@ -0,0 +1,62 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , color = Base.color;
+
+/**
+ * Expose `List`.
+ */
+
+exports = module.exports = List;
+
+/**
+ * Initialize a new `List` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function List(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , total = runner.total;
+
+ runner.on('start', function(){
+ console.log(JSON.stringify(['start', { total: total }]));
+ });
+
+ runner.on('pass', function(test){
+ console.log(JSON.stringify(['pass', clean(test)]));
+ });
+
+ runner.on('fail', function(test, err){
+ test = clean(test);
+ test.err = err.message;
+ console.log(JSON.stringify(['fail', test]));
+ });
+
+ runner.on('end', function(){
+ process.stdout.write(JSON.stringify(['end', self.stats]));
+ });
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+ return {
+ title: test.title
+ , fullTitle: test.fullTitle()
+ , duration: test.duration
+ }
+}
diff --git a/lib/reporters/json.js b/lib/reporters/json.js
new file mode 100644
index 0000000..f565506
--- /dev/null
+++ b/lib/reporters/json.js
@@ -0,0 +1,92 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `JSON`.
+ */
+
+exports = module.exports = JSONReporter;
+
+/**
+ * Initialize a new `JSON` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function JSONReporter(runner) {
+ var self = this;
+ Base.call(this, runner);
+
+ var tests = []
+ , pending = []
+ , failures = []
+ , passes = [];
+
+ runner.on('test end', function(test){
+ tests.push(test);
+ });
+
+ runner.on('pass', function(test){
+ passes.push(test);
+ });
+
+ runner.on('fail', function(test){
+ failures.push(test);
+ });
+
+ runner.on('pending', function(test){
+ pending.push(test);
+ });
+
+ runner.on('end', function(){
+ var obj = {
+ stats: self.stats,
+ tests: tests.map(clean),
+ pending: pending.map(clean),
+ failures: failures.map(clean),
+ passes: passes.map(clean)
+ };
+
+ runner.testResults = obj;
+
+ process.stdout.write(JSON.stringify(obj, null, 2));
+ });
+}
+
+/**
+ * Return a plain-object representation of `test`
+ * free of cyclic properties etc.
+ *
+ * @param {Object} test
+ * @return {Object}
+ * @api private
+ */
+
+function clean(test) {
+ return {
+ title: test.title,
+ fullTitle: test.fullTitle(),
+ duration: test.duration,
+ err: errorJSON(test.err || {})
+ }
+}
+
+/**
+ * Transform `error` into a JSON object.
+ * @param {Error} err
+ * @return {Object}
+ */
+
+function errorJSON(err) {
+ var res = {};
+ Object.getOwnPropertyNames(err).forEach(function(key) {
+ res[key] = err[key];
+ }, err);
+ return res;
+}
diff --git a/lib/reporters/landing.js b/lib/reporters/landing.js
new file mode 100644
index 0000000..ee004a2
--- /dev/null
+++ b/lib/reporters/landing.js
@@ -0,0 +1,96 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `Landing`.
+ */
+
+exports = module.exports = Landing;
+
+/**
+ * Airplane color.
+ */
+
+Base.colors.plane = 0;
+
+/**
+ * Airplane crash color.
+ */
+
+Base.colors['plane crash'] = 31;
+
+/**
+ * Runway color.
+ */
+
+Base.colors.runway = 90;
+
+/**
+ * Initialize a new `Landing` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Landing(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .75 | 0
+ , total = runner.total
+ , stream = process.stdout
+ , plane = color('plane', '✈')
+ , crashed = -1
+ , n = 0;
+
+ function runway() {
+ var buf = Array(width).join('-');
+ return ' ' + color('runway', buf);
+ }
+
+ runner.on('start', function(){
+ stream.write('\n\n\n ');
+ cursor.hide();
+ });
+
+ runner.on('test end', function(test){
+ // check if the plane crashed
+ var col = -1 == crashed
+ ? width * ++n / total | 0
+ : crashed;
+
+ // show the crash
+ if ('failed' == test.state) {
+ plane = color('plane crash', '✈');
+ crashed = col;
+ }
+
+ // render landing strip
+ stream.write('\u001b['+(width+1)+'D\u001b[2A');
+ stream.write(runway());
+ stream.write('\n ');
+ stream.write(color('runway', Array(col).join('⋅')));
+ stream.write(plane)
+ stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
+ stream.write(runway());
+ stream.write('\u001b[0m');
+ });
+
+ runner.on('end', function(){
+ cursor.show();
+ console.log();
+ self.epilogue();
+ });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+Landing.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/list.js b/lib/reporters/list.js
new file mode 100644
index 0000000..f64367a
--- /dev/null
+++ b/lib/reporters/list.js
@@ -0,0 +1,63 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `List`.
+ */
+
+exports = module.exports = List;
+
+/**
+ * Initialize a new `List` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function List(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , n = 0;
+
+ runner.on('start', function(){
+ console.log();
+ });
+
+ runner.on('test', function(test){
+ process.stdout.write(color('pass', ' ' + test.fullTitle() + ': '));
+ });
+
+ runner.on('pending', function(test){
+ var fmt = color('checkmark', ' -')
+ + color('pending', ' %s');
+ console.log(fmt, test.fullTitle());
+ });
+
+ runner.on('pass', function(test){
+ var fmt = color('checkmark', ' '+Base.symbols.dot)
+ + color('pass', ' %s: ')
+ + color(test.speed, '%dms');
+ cursor.CR();
+ console.log(fmt, test.fullTitle(), test.duration);
+ });
+
+ runner.on('fail', function(test, err){
+ cursor.CR();
+ console.log(color('fail', ' %d) %s'), ++n, test.fullTitle());
+ });
+
+ runner.on('end', self.epilogue.bind(self));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+List.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/markdown.js b/lib/reporters/markdown.js
new file mode 100644
index 0000000..e14174c
--- /dev/null
+++ b/lib/reporters/markdown.js
@@ -0,0 +1,100 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils');
+
+/**
+ * Constants
+ */
+
+var SUITE_PREFIX = '$';
+
+/**
+ * Expose `Markdown`.
+ */
+
+exports = module.exports = Markdown;
+
+/**
+ * Initialize a new `Markdown` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Markdown(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , level = 0
+ , buf = '';
+
+ function title(str) {
+ return Array(level).join('#') + ' ' + str;
+ }
+
+ function indent() {
+ return Array(level).join(' ');
+ }
+
+ function mapTOC(suite, obj) {
+ var ret = obj,
+ key = SUITE_PREFIX + suite.title;
+ obj = obj[key] = obj[key] || { suite: suite };
+ suite.suites.forEach(function(suite){
+ mapTOC(suite, obj);
+ });
+ return ret;
+ }
+
+ function stringifyTOC(obj, level) {
+ ++level;
+ var buf = '';
+ var link;
+ for (var key in obj) {
+ if ('suite' == key) continue;
+ if (key !== SUITE_PREFIX) {
+ link = ' - [' + key.substring(1) + ']';
+ link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n';
+ buf += Array(level).join(' ') + link;
+ }
+ buf += stringifyTOC(obj[key], level);
+ }
+ return buf;
+ }
+
+ function generateTOC(suite) {
+ var obj = mapTOC(suite, {});
+ return stringifyTOC(obj, 0);
+ }
+
+ generateTOC(runner.suite);
+
+ runner.on('suite', function(suite){
+ ++level;
+ var slug = utils.slug(suite.fullTitle());
+ buf += '<a name="' + slug + '"></a>' + '\n';
+ buf += title(suite.title) + '\n';
+ });
+
+ runner.on('suite end', function(suite){
+ --level;
+ });
+
+ runner.on('pass', function(test){
+ var code = utils.clean(test.fn.toString());
+ buf += test.title + '.\n';
+ buf += '\n```js\n';
+ buf += code + '\n';
+ buf += '```\n\n';
+ });
+
+ runner.on('end', function(){
+ process.stdout.write('# TOC\n');
+ process.stdout.write(generateTOC(runner.suite));
+ process.stdout.write(buf);
+ });
+}
diff --git a/lib/reporters/min.js b/lib/reporters/min.js
new file mode 100644
index 0000000..ce1a3fe
--- /dev/null
+++ b/lib/reporters/min.js
@@ -0,0 +1,37 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `Min`.
+ */
+
+exports = module.exports = Min;
+
+/**
+ * Initialize a new `Min` minimal test reporter (best used with --watch).
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Min(runner) {
+ Base.call(this, runner);
+
+ runner.on('start', function(){
+ // clear screen
+ process.stdout.write('\u001b[2J');
+ // set cursor position
+ process.stdout.write('\u001b[1;3H');
+ });
+
+ runner.on('end', this.epilogue.bind(this));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+Min.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/nyan.js b/lib/reporters/nyan.js
new file mode 100644
index 0000000..63056b1
--- /dev/null
+++ b/lib/reporters/nyan.js
@@ -0,0 +1,260 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base');
+
+/**
+ * Expose `Dot`.
+ */
+
+exports = module.exports = NyanCat;
+
+/**
+ * Initialize a new `Dot` matrix test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function NyanCat(runner) {
+ Base.call(this, runner);
+ var self = this
+ , stats = this.stats
+ , width = Base.window.width * .75 | 0
+ , rainbowColors = this.rainbowColors = self.generateColors()
+ , colorIndex = this.colorIndex = 0
+ , numerOfLines = this.numberOfLines = 4
+ , trajectories = this.trajectories = [[], [], [], []]
+ , nyanCatWidth = this.nyanCatWidth = 11
+ , trajectoryWidthMax = this.trajectoryWidthMax = (width - nyanCatWidth)
+ , scoreboardWidth = this.scoreboardWidth = 5
+ , tick = this.tick = 0
+ , n = 0;
+
+ runner.on('start', function(){
+ Base.cursor.hide();
+ self.draw();
+ });
+
+ runner.on('pending', function(test){
+ self.draw();
+ });
+
+ runner.on('pass', function(test){
+ self.draw();
+ });
+
+ runner.on('fail', function(test, err){
+ self.draw();
+ });
+
+ runner.on('end', function(){
+ Base.cursor.show();
+ for (var i = 0; i < self.numberOfLines; i++) write('\n');
+ self.epilogue();
+ });
+}
+
+/**
+ * Draw the nyan cat
+ *
+ * @api private
+ */
+
+NyanCat.prototype.draw = function(){
+ this.appendRainbow();
+ this.drawScoreboard();
+ this.drawRainbow();
+ this.drawNyanCat();
+ this.tick = !this.tick;
+};
+
+/**
+ * Draw the "scoreboard" showing the number
+ * of passes, failures and pending tests.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawScoreboard = function(){
+ var stats = this.stats;
+
+ function draw(type, n) {
+ write(' ');
+ write(Base.color(type, n));
+ write('\n');
+ }
+
+ draw('green', stats.passes);
+ draw('fail', stats.failures);
+ draw('pending', stats.pending);
+ write('\n');
+
+ this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Append the rainbow.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.appendRainbow = function(){
+ var segment = this.tick ? '_' : '-';
+ var rainbowified = this.rainbowify(segment);
+
+ for (var index = 0; index < this.numberOfLines; index++) {
+ var trajectory = this.trajectories[index];
+ if (trajectory.length >= this.trajectoryWidthMax) trajectory.shift();
+ trajectory.push(rainbowified);
+ }
+};
+
+/**
+ * Draw the rainbow.
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawRainbow = function(){
+ var self = this;
+
+ this.trajectories.forEach(function(line, index) {
+ write('\u001b[' + self.scoreboardWidth + 'C');
+ write(line.join(''));
+ write('\n');
+ });
+
+ this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Draw the nyan cat
+ *
+ * @api private
+ */
+
+NyanCat.prototype.drawNyanCat = function() {
+ var self = this;
+ var startWidth = this.scoreboardWidth + this.trajectories[0].length;
+ var dist = '\u001b[' + startWidth + 'C';
+ var padding = '';
+
+ write(dist);
+ write('_,------,');
+ write('\n');
+
+ write(dist);
+ padding = self.tick ? ' ' : ' ';
+ write('_|' + padding + '/\\_/\\ ');
+ write('\n');
+
+ write(dist);
+ padding = self.tick ? '_' : '__';
+ var tail = self.tick ? '~' : '^';
+ var face;
+ write(tail + '|' + padding + this.face() + ' ');
+ write('\n');
+
+ write(dist);
+ padding = self.tick ? ' ' : ' ';
+ write(padding + '"" "" ');
+ write('\n');
+
+ this.cursorUp(this.numberOfLines);
+};
+
+/**
+ * Draw nyan cat face.
+ *
+ * @return {String}
+ * @api private
+ */
+
+NyanCat.prototype.face = function() {
+ var stats = this.stats;
+ if (stats.failures) {
+ return '( x .x)';
+ } else if (stats.pending) {
+ return '( o .o)';
+ } else if(stats.passes) {
+ return '( ^ .^)';
+ } else {
+ return '( - .-)';
+ }
+};
+
+/**
+ * Move cursor up `n`.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+NyanCat.prototype.cursorUp = function(n) {
+ write('\u001b[' + n + 'A');
+};
+
+/**
+ * Move cursor down `n`.
+ *
+ * @param {Number} n
+ * @api private
+ */
+
+NyanCat.prototype.cursorDown = function(n) {
+ write('\u001b[' + n + 'B');
+};
+
+/**
+ * Generate rainbow colors.
+ *
+ * @return {Array}
+ * @api private
+ */
+
+NyanCat.prototype.generateColors = function(){
+ var colors = [];
+
+ for (var i = 0; i < (6 * 7); i++) {
+ var pi3 = Math.floor(Math.PI / 3);
+ var n = (i * (1.0 / 6));
+ var r = Math.floor(3 * Math.sin(n) + 3);
+ var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3);
+ var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3);
+ colors.push(36 * r + 6 * g + b + 16);
+ }
+
+ return colors;
+};
+
+/**
+ * Apply rainbow to the given `str`.
+ *
+ * @param {String} str
+ * @return {String}
+ * @api private
+ */
+
+NyanCat.prototype.rainbowify = function(str){
+ if (!Base.useColors)
+ return str;
+ var color = this.rainbowColors[this.colorIndex % this.rainbowColors.length];
+ this.colorIndex += 1;
+ return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m';
+};
+
+/**
+ * Stdout helper.
+ */
+
+function write(string) {
+ process.stdout.write(string);
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+NyanCat.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/progress.js b/lib/reporters/progress.js
new file mode 100644
index 0000000..2debb94
--- /dev/null
+++ b/lib/reporters/progress.js
@@ -0,0 +1,92 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `Progress`.
+ */
+
+exports = module.exports = Progress;
+
+/**
+ * General progress bar color.
+ */
+
+Base.colors.progress = 90;
+
+/**
+ * Initialize a new `Progress` bar test reporter.
+ *
+ * @param {Runner} runner
+ * @param {Object} options
+ * @api public
+ */
+
+function Progress(runner, options) {
+ Base.call(this, runner);
+
+ var self = this
+ , options = options || {}
+ , stats = this.stats
+ , width = Base.window.width * .50 | 0
+ , total = runner.total
+ , complete = 0
+ , max = Math.max
+ , lastN = -1;
+
+ // default chars
+ options.open = options.open || '[';
+ options.complete = options.complete || '▬';
+ options.incomplete = options.incomplete || Base.symbols.dot;
+ options.close = options.close || ']';
+ options.verbose = false;
+
+ // tests started
+ runner.on('start', function(){
+ console.log();
+ cursor.hide();
+ });
+
+ // tests complete
+ runner.on('test end', function(){
+ complete++;
+ var incomplete = total - complete
+ , percent = complete / total
+ , n = width * percent | 0
+ , i = width - n;
+
+ if (lastN === n && !options.verbose) {
+ // Don't re-render the line if it hasn't changed
+ return;
+ }
+ lastN = n;
+
+ cursor.CR();
+ process.stdout.write('\u001b[J');
+ process.stdout.write(color('progress', ' ' + options.open));
+ process.stdout.write(Array(n).join(options.complete));
+ process.stdout.write(Array(i).join(options.incomplete));
+ process.stdout.write(color('progress', options.close));
+ if (options.verbose) {
+ process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
+ }
+ });
+
+ // tests are complete, output some stats
+ // and the failures if any
+ runner.on('end', function(){
+ cursor.show();
+ console.log();
+ self.epilogue();
+ });
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+Progress.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/spec.js b/lib/reporters/spec.js
new file mode 100644
index 0000000..3debffe
--- /dev/null
+++ b/lib/reporters/spec.js
@@ -0,0 +1,82 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `Spec`.
+ */
+
+exports = module.exports = Spec;
+
+/**
+ * Initialize a new `Spec` test reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function Spec(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , indents = 0
+ , n = 0;
+
+ function indent() {
+ return Array(indents).join(' ')
+ }
+
+ runner.on('start', function(){
+ console.log();
+ });
+
+ runner.on('suite', function(suite){
+ ++indents;
+ console.log(color('suite', '%s%s'), indent(), suite.title);
+ });
+
+ runner.on('suite end', function(suite){
+ --indents;
+ if (1 == indents) console.log();
+ });
+
+ runner.on('pending', function(test){
+ var fmt = indent() + color('pending', ' - %s');
+ console.log(fmt, test.title);
+ });
+
+ runner.on('pass', function(test){
+ if ('fast' == test.speed) {
+ var fmt = indent()
+ + color('checkmark', ' ' + Base.symbols.ok)
+ + color('pass', ' %s');
+ cursor.CR();
+ console.log(fmt, test.title);
+ } else {
+ var fmt = indent()
+ + color('checkmark', ' ' + Base.symbols.ok)
+ + color('pass', ' %s')
+ + color(test.speed, ' (%dms)');
+ cursor.CR();
+ console.log(fmt, test.title, test.duration);
+ }
+ });
+
+ runner.on('fail', function(test, err){
+ cursor.CR();
+ console.log(indent() + color('fail', ' %d) %s'), ++n, test.title);
+ });
+
+ runner.on('end', self.epilogue.bind(self));
+}
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+Spec.prototype.__proto__ = Base.prototype;
diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js
new file mode 100644
index 0000000..01a92eb
--- /dev/null
+++ b/lib/reporters/tap.js
@@ -0,0 +1,72 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+/**
+ * Expose `TAP`.
+ */
+
+exports = module.exports = TAP;
+
+/**
+ * Initialize a new `TAP` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function TAP(runner) {
+ Base.call(this, runner);
+
+ var self = this
+ , stats = this.stats
+ , n = 1
+ , passes = 0
+ , failures = 0;
+
+ runner.on('start', function(){
+ var total = runner.grepTotal(runner.suite);
+ console.log('%d..%d', 1, total);
+ });
+
+ runner.on('test end', function(){
+ ++n;
+ });
+
+ runner.on('pending', function(test){
+ console.log('ok %d %s # SKIP -', n, title(test));
+ });
+
+ runner.on('pass', function(test){
+ passes++;
+ console.log('ok %d %s', n, title(test));
+ });
+
+ runner.on('fail', function(test, err){
+ failures++;
+ console.log('not ok %d %s', n, title(test));
+ if (err.stack) console.log(err.stack.replace(/^/gm, ' '));
+ });
+
+ runner.on('end', function(){
+ console.log('# tests ' + (passes + failures));
+ console.log('# pass ' + passes);
+ console.log('# fail ' + failures);
+ });
+}
+
+/**
+ * Return a TAP-safe title of `test`
+ *
+ * @param {Object} test
+ * @return {String}
+ * @api private
+ */
+
+function title(test) {
+ return test.fullTitle().replace(/#/g, '');
+}
diff --git a/lib/reporters/templates/coverage.jade b/lib/reporters/templates/coverage.jade
new file mode 100644
index 0000000..edd59d8
--- /dev/null
+++ b/lib/reporters/templates/coverage.jade
@@ -0,0 +1,51 @@
+doctype html
+html
+ head
+ title Coverage
+ meta(charset='utf-8')
+ include script.html
+ include style.html
+ body
+ #coverage
+ h1#overview Coverage
+ include menu
+
+ #stats(class=coverageClass(cov.coverage))
+ .percentage #{cov.coverage | 0}%
+ .sloc= cov.sloc
+ .hits= cov.hits
+ .misses= cov.misses
+
+ #files
+ for file in cov.files
+ .file
+ h2(id=file.filename)= file.filename
+ #stats(class=coverageClass(file.coverage))
+ .percentage #{file.coverage | 0}%
+ .sloc= file.sloc
+ .hits= file.hits
+ .misses= file.misses
+
+ table#source
+ thead
+ tr
+ th Line
+ th Hits
+ th Source
+ tbody
+ for line, number in file.source
+ if line.coverage > 0
+ tr.hit
+ td.line= number
+ td.hits= line.coverage
+ td.source= line.source
+ else if 0 === line.coverage
+ tr.miss
+ td.line= number
+ td.hits 0
+ td.source= line.source
+ else
+ tr
+ td.line= number
+ td.hits
+ td.source= line.source || ' '
diff --git a/lib/reporters/templates/menu.jade b/lib/reporters/templates/menu.jade
new file mode 100644
index 0000000..e9ba464
--- /dev/null
+++ b/lib/reporters/templates/menu.jade
@@ -0,0 +1,13 @@
+#menu
+ li
+ a(href='#overview') overview
+ for file in cov.files
+ li
+ span.cov(class=coverageClass(file.coverage)) #{file.coverage | 0}
+ a(href='##{file.filename}')
+ segments = file.filename.split('/')
+ basename = segments.pop()
+ if segments.length
+ span.dirname= segments.join('/') + '/'
+ span.basename= basename
+ a#logo(href='http://visionmedia.github.io/mocha/') m
diff --git a/lib/reporters/templates/script.html b/lib/reporters/templates/script.html
new file mode 100644
index 0000000..073cf79
--- /dev/null
+++ b/lib/reporters/templates/script.html
@@ -0,0 +1,34 @@
+<script>
+
+headings = [];
+
+onload = function(){
+ headings = document.querySelectorAll('h2');
+};
+
+onscroll = function(e){
+ var heading = find(window.scrollY);
+ if (!heading) return;
+ var links = document.querySelectorAll('#menu a')
+ , link;
+
+ for (var i = 0, len = links.length; i < len; ++i) {
+ link = links[i];
+ link.className = link.getAttribute('href') == '#' + heading.id
+ ? 'active'
+ : '';
+ }
+};
+
+function find(y) {
+ var i = headings.length
+ , heading;
+
+ while (i--) {
+ heading = headings[i];
+ if (y >= heading.offsetTop) {
+ return heading;
+ }
+ }
+}
+</script>
diff --git a/lib/reporters/templates/style.html b/lib/reporters/templates/style.html
new file mode 100644
index 0000000..4c9c37c
--- /dev/null
+++ b/lib/reporters/templates/style.html
@@ -0,0 +1,324 @@
+<style>
+
+body {
+ font: 14px/1.6 "Helvetica Neue", Helvetica, Arial, sans-serif;
+ margin: 0;
+ color: #2C2C2C;
+ border-top: 2px solid #ddd;
+}
+
+#coverage {
+ padding: 60px 400px 60px 60px;
+}
+
+h1 a {
+ color: inherit;
+ font-weight: inherit;
+}
+
+h1 a:hover {
+ text-decoration: none;
+}
+
+.onload h1 {
+ opacity: 1;
+}
+
+h2 {
+ width: 80%;
+ margin-top: 80px;
+ margin-bottom: 0;
+ font-weight: 100;
+ letter-spacing: 1px;
+ border-bottom: 1px solid #eee;
+}
+
+a {
+ color: #8A6343;
+ font-weight: bold;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+ul {
+ margin-top: 20px;
+ padding: 0 15px;
+ width: 100%;
+}
+
+ul li {
+ float: left;
+ width: 40%;
+ margin-top: 5px;
+ margin-right: 60px;
+ list-style: none;
+ border-bottom: 1px solid #eee;
+ padding: 5px 0;
+ font-size: 12px;
+}
+
+ul::after {
+ content: '.';
+ height: 0;
+ display: block;
+ visibility: hidden;
+ clear: both;
+}
+
+code {
+ font: 12px monaco, monospace;
+}
+
+pre {
+ margin: 30px;
+ padding: 30px;
+ border: 1px solid #eee;
+ border-bottom-color: #ddd;
+ -webkit-border-radius: 2px;
+ -moz-border-radius: 2px;
+ border-radius: 2px;
+ -webkit-box-shadow: inset 0 0 10px #eee;
+ -moz-box-shadow: inset 0 0 10px #eee;
+ box-shadow: inset 0 0 10px #eee;
+ overflow-x: auto;
+}
+
+img {
+ margin: 30px;
+ padding: 1px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ -webkit-box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
+ -moz-box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
+ box-shadow: 0 3px 10px #dedede, 0 1px 5px #888;
+ max-width: 100%;
+}
+
+footer {
+ background: #eee;
+ width: 100%;
+ padding: 50px 0;
+ text-align: right;
+ border-top: 1px solid #ddd;
+}
+
+footer span {
+ display: block;
+ margin-right: 30px;
+ color: #888;
+ font-size: 12px;
+}
+
+#menu {
+ position: fixed;
+ font-size: 12px;
+ overflow-y: auto;
+ top: 0;
+ right: 0;
+ margin: 0;
+ height: 100%;
+ padding: 15px 0;
+ text-align: right;
+ border-left: 1px solid #eee;
+ max-width: 400px;
+ overflow: auto;
+ white-space: nowrap;
+
+ -moz-box-shadow: 0 0 2px #888
+ , inset 5px 0 20px rgba(0,0,0,.5)
+ , inset 5px 0 3px rgba(0,0,0,.3);
+ -webkit-box-shadow: 0 0 2px #888
+ , inset 5px 0 20px rgba(0,0,0,.5)
+ , inset 5px 0 3px rgba(0,0,0,.3);
+ box-shadow: 0 0 2px #888
+ , inset 5px 0 20px rgba(0,0,0,.5)
+ , inset 5px 0 3px rgba(0,0,0,.3);
+ -webkit-font-smoothing: antialiased;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAABmCAMAAAAOARRQAAABelBMVEUjJSU6OzshIyM5OjoqKy02NjgsLS01NTYjJCUzNTUgISMlJSc0NTUvMDA6PDwlJyg1NjYoKis2NjYrLS02ODkpKyw0NDYrLC04ODovLzA4Ojo0NDUtLy86OjwjIyU4OTosLS82ODgtLS8hIyQvMTEnKCooKSsrKy0qLCwkJSUnKCkrLCwpKiwwMjIxMzMqLC0tLS0pKissLC00NTYwMDIwMTQpKysoKSovMDEtLzA2OTkxMzUrKywvLy8qKyszNTY5OzsqKiw6OjswMDExNDUoKiozNDUvMDIyNDY1Njg2Njk5OTozMzU0NjY4ODkiIyUiIyQ4OTkuMDEmKCowMjQwMTErLS4qKywwMTMhIiMpKiopKy0tLjAkJScxNDQvLzExNDYyNDQmKCk [...]
+}
+
+#menu::after {
+ display: block;
+ content: '';
+ padding-top: 80px;
+}
+
+#logo {
+ position: fixed;
+ bottom: 10px;
+ right: 10px;
+ background: rgba(255,255,255,.1);
+ font-size: 11px;
+ display: block;
+ width: 20px;
+ height: 20px;
+ line-height: 20px;
+ text-align: center;
+ -webkit-border-radius: 20px;
+ -moz-border-radius: 20px;
+ border-radius: 20px;
+ -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2);
+ -moz-box-shadow: 0 0 3px rgba(0,0,0,.2);
+ box-shadow: 0 0 3px rgba(0,0,0,.2);
+ color: inherit;
+}
+
+#menu li a {
+ display: block;
+ color: white;
+ padding: 0 35px 0 25px;
+ -webkit-transition: background 300ms;
+ -moz-transition: background 300ms;
+}
+
+#menu li {
+ position: relative;
+ list-style: none;
+}
+
+#menu a:hover,
+#menu a.active {
+ text-decoration: none;
+ background: rgba(255,255,255,.1);
+}
+
+#menu li:hover .cov {
+ opacity: 1;
+}
+
+#menu li .dirname {
+ opacity: .60;
+ padding-right: 2px;
+}
+
+#menu li .basename {
+ opacity: 1;
+}
+
+#menu .cov {
+ background: rgba(0,0,0,.4);
+ position: absolute;
+ top: 0;
+ right: 8px;
+ font-size: 9px;
+ opacity: .6;
+ text-align: left;
+ width: 17px;
+ -webkit-border-radius: 10px;
+ -moz-border-radius: 10px;
+ border-radius: 10px;
+ padding: 2px 3px;
+ text-align: center;
+}
+
+#stats:nth-child(2n) {
+ display: inline-block;
+ margin-top: 15px;
+ border: 1px solid #eee;
+ padding: 10px;
+ -webkit-box-shadow: inset 0 0 2px #eee;
+ -moz-box-shadow: inset 0 0 2px #eee;
+ box-shadow: inset 0 0 2px #eee;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+#stats div {
+ float: left;
+ padding: 0 5px;
+}
+
+#stats::after {
+ display: block;
+ content: '';
+ clear: both;
+}
+
+#stats .sloc::after {
+ content: ' SLOC';
+ color: #b6b6b6;
+}
+
+#stats .percentage::after {
+ content: ' coverage';
+ color: #b6b6b6;
+}
+
+#stats .hits,
+#stats .misses {
+ display: none;
+}
+
+.high {
+ color: #00d4b4;
+}
+.medium {
+ color: #e87d0d;
+}
+.low {
+ color: #d4081a;
+}
+.terrible {
+ color: #d4081a;
+ font-weight: bold;
+}
+
+table {
+ width: 80%;
+ margin-top: 10px;
+ border-collapse: collapse;
+ border: 1px solid #cbcbcb;
+ color: #363636;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+table thead {
+ display: none;
+}
+
+table td.line,
+table td.hits {
+ width: 20px;
+ background: #eaeaea;
+ text-align: center;
+ font-size: 11px;
+ padding: 0 10px;
+ color: #949494;
+}
+
+table td.hits {
+ width: 10px;
+ padding: 2px 5px;
+ color: rgba(0,0,0,.2);
+ background: #f0f0f0;
+}
+
+tr.miss td.line,
+tr.miss td.hits {
+ background: #e6c3c7;
+}
+
+tr.miss td {
+ background: #f8d5d8;
+}
+
+td.source {
+ padding-left: 15px;
+ line-height: 15px;
+ white-space: pre;
+ font: 12px monaco, monospace;
+}
+
+code .comment { color: #ddd }
+code .init { color: #2F6FAD }
+code .string { color: #5890AD }
+code .keyword { color: #8A6343 }
+code .number { color: #2F6FAD }
+</style>
diff --git a/lib/reporters/xunit.js b/lib/reporters/xunit.js
new file mode 100644
index 0000000..77cd347
--- /dev/null
+++ b/lib/reporters/xunit.js
@@ -0,0 +1,149 @@
+/**
+ * Module dependencies.
+ */
+
+var Base = require('./base')
+ , utils = require('../utils')
+ , fs = require('fs')
+ , escape = utils.escape;
+
+/**
+ * Save timer references to avoid Sinon interfering (see GH-237).
+ */
+
+var Date = global.Date
+ , setTimeout = global.setTimeout
+ , setInterval = global.setInterval
+ , clearTimeout = global.clearTimeout
+ , clearInterval = global.clearInterval;
+
+/**
+ * Expose `XUnit`.
+ */
+
+exports = module.exports = XUnit;
+
+/**
+ * Initialize a new `XUnit` reporter.
+ *
+ * @param {Runner} runner
+ * @api public
+ */
+
+function XUnit(runner, options) {
+ Base.call(this, runner);
+ var stats = this.stats
+ , tests = []
+ , self = this;
+
+ if (options.reporterOptions && options.reporterOptions.output) {
+ if (! fs.createWriteStream) {
+ throw new Error('file output not supported in browser');
+ }
+ self.fileStream = fs.createWriteStream(options.reporterOptions.output);
+ }
+
+ runner.on('pending', function(test){
+ tests.push(test);
+ });
+
+ runner.on('pass', function(test){
+ tests.push(test);
+ });
+
+ runner.on('fail', function(test){
+ tests.push(test);
+ });
+
+ runner.on('end', function(){
+ self.write(tag('testsuite', {
+ name: 'Mocha Tests'
+ , tests: stats.tests
+ , failures: stats.failures
+ , errors: stats.failures
+ , skipped: stats.tests - stats.failures - stats.passes
+ , timestamp: (new Date).toUTCString()
+ , time: (stats.duration / 1000) || 0
+ }, false));
+
+ tests.forEach(function(t) { self.test(t); });
+ self.write('</testsuite>');
+ });
+}
+
+/**
+ * Override done to close the stream (if it's a file).
+ */
+XUnit.prototype.done = function(failures, fn) {
+ if (this.fileStream) {
+ this.fileStream.end(function() {
+ fn(failures);
+ });
+ } else {
+ fn(failures);
+ }
+};
+
+/**
+ * Inherit from `Base.prototype`.
+ */
+
+XUnit.prototype.__proto__ = Base.prototype;
+
+/**
+ * Write out the given line
+ */
+XUnit.prototype.write = function(line) {
+ if (this.fileStream) {
+ this.fileStream.write(line + '\n');
+ } else {
+ console.log(line);
+ }
+};
+
+/**
+ * Output tag for the given `test.`
+ */
+
+XUnit.prototype.test = function(test, ostream) {
+ var attrs = {
+ classname: test.parent.fullTitle()
+ , name: test.title
+ , time: (test.duration / 1000) || 0
+ };
+
+ if ('failed' == test.state) {
+ var err = test.err;
+ this.write(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
+ } else if (test.pending) {
+ this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
+ } else {
+ this.write(tag('testcase', attrs, true) );
+ }
+};
+
+/**
+ * HTML tag helper.
+ */
+
+function tag(name, attrs, close, content) {
+ var end = close ? '/>' : '>'
+ , pairs = []
+ , tag;
+
+ for (var key in attrs) {
+ pairs.push(key + '="' + escape(attrs[key]) + '"');
+ }
+
+ tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
+ if (content) tag += content + '</' + name + end;
+ return tag;
+}
+
+/**
+ * Return cdata escaped CDATA `str`.
+ */
+
+function cdata(str) {
+ return '<![CDATA[' + escape(str) + ']]>';
+}
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-tap-mocha-reporter.git
More information about the Pkg-javascript-commits
mailing list