Current Dev State

This commit is contained in:
Tim Lorsbach
2025-06-23 20:13:54 +02:00
parent b4f9bb277d
commit ded50edaa2
22617 changed files with 4345095 additions and 174 deletions

View File

@ -0,0 +1,4 @@
module.exports = function(type){
return function(){
}
};

View File

@ -0,0 +1,11 @@
'use strict';
var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
module.exports = function (str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.replace(matchOperatorsRe, '\\$&');
};

View File

@ -0,0 +1,177 @@
/**
* Module exports.
*/
exports.EventEmitter = EventEmitter;
/**
* Check if `obj` is an array.
*/
function isArray(obj) {
return '[object Array]' == {}.toString.call(obj);
}
/**
* Event emitter constructor.
*
* @api public
*/
function EventEmitter(){};
/**
* Adds a listener.
*
* @api public
*/
EventEmitter.prototype.on = function (name, fn) {
if (!this.$events) {
this.$events = {};
}
if (!this.$events[name]) {
this.$events[name] = fn;
} else if (isArray(this.$events[name])) {
this.$events[name].push(fn);
} else {
this.$events[name] = [this.$events[name], fn];
}
return this;
};
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
/**
* Adds a volatile listener.
*
* @api public
*/
EventEmitter.prototype.once = function (name, fn) {
var self = this;
function on () {
self.removeListener(name, on);
fn.apply(this, arguments);
};
on.listener = fn;
this.on(name, on);
return this;
};
/**
* Removes a listener.
*
* @api public
*/
EventEmitter.prototype.removeListener = function (name, fn) {
if (this.$events && this.$events[name]) {
var list = this.$events[name];
if (isArray(list)) {
var pos = -1;
for (var i = 0, l = list.length; i < l; i++) {
if (list[i] === fn || (list[i].listener && list[i].listener === fn)) {
pos = i;
break;
}
}
if (pos < 0) {
return this;
}
list.splice(pos, 1);
if (!list.length) {
delete this.$events[name];
}
} else if (list === fn || (list.listener && list.listener === fn)) {
delete this.$events[name];
}
}
return this;
};
/**
* Removes all listeners for an event.
*
* @api public
*/
EventEmitter.prototype.removeAllListeners = function (name) {
if (name === undefined) {
this.$events = {};
return this;
}
if (this.$events && this.$events[name]) {
this.$events[name] = null;
}
return this;
};
/**
* Gets all listeners for a certain event.
*
* @api public
*/
EventEmitter.prototype.listeners = function (name) {
if (!this.$events) {
this.$events = {};
}
if (!this.$events[name]) {
this.$events[name] = [];
}
if (!isArray(this.$events[name])) {
this.$events[name] = [this.$events[name]];
}
return this.$events[name];
};
/**
* Emits an event.
*
* @api public
*/
EventEmitter.prototype.emit = function (name) {
if (!this.$events) {
return false;
}
var handler = this.$events[name];
if (!handler) {
return false;
}
var args = [].slice.call(arguments, 1);
if ('function' == typeof handler) {
handler.apply(this, args);
} else if (isArray(handler)) {
var listeners = handler.slice();
for (var i = 0, l = listeners.length; i < l; i++) {
listeners[i].apply(this, args);
}
} else {
return false;
}
return true;
};

View File

View File

@ -0,0 +1,125 @@
/**
* Expose `Progress`.
*/
module.exports = Progress;
/**
* Initialize a new `Progress` indicator.
*/
function Progress() {
this.percent = 0;
this.size(0);
this.fontSize(11);
this.font('helvetica, arial, sans-serif');
}
/**
* Set progress size to `n`.
*
* @param {Number} n
* @return {Progress} for chaining
* @api public
*/
Progress.prototype.size = function(n){
this._size = n;
return this;
};
/**
* Set text to `str`.
*
* @param {String} str
* @return {Progress} for chaining
* @api public
*/
Progress.prototype.text = function(str){
this._text = str;
return this;
};
/**
* Set font size to `n`.
*
* @param {Number} n
* @return {Progress} for chaining
* @api public
*/
Progress.prototype.fontSize = function(n){
this._fontSize = n;
return this;
};
/**
* Set font `family`.
*
* @param {String} family
* @return {Progress} for chaining
*/
Progress.prototype.font = function(family){
this._font = family;
return this;
};
/**
* Update percentage to `n`.
*
* @param {Number} n
* @return {Progress} for chaining
*/
Progress.prototype.update = function(n){
this.percent = n;
return this;
};
/**
* Draw on `ctx`.
*
* @param {CanvasRenderingContext2d} ctx
* @return {Progress} for chaining
*/
Progress.prototype.draw = function(ctx){
try {
var percent = Math.min(this.percent, 100)
, size = this._size
, half = size / 2
, x = half
, y = half
, rad = half - 1
, fontSize = this._fontSize;
ctx.font = fontSize + 'px ' + this._font;
var angle = Math.PI * 2 * (percent / 100);
ctx.clearRect(0, 0, size, size);
// outer circle
ctx.strokeStyle = '#9f9f9f';
ctx.beginPath();
ctx.arc(x, y, rad, 0, angle, false);
ctx.stroke();
// inner circle
ctx.strokeStyle = '#eee';
ctx.beginPath();
ctx.arc(x, y, rad - 1, 0, angle, true);
ctx.stroke();
// text
var text = this._text || (percent | 0) + '%'
, w = ctx.measureText(text).width;
ctx.fillText(
text
, x - w / 2 + 1
, y + fontSize / 2 - 1);
} catch (ex) {} //don't fail if we can't render progress
return this;
};

View File

@ -0,0 +1,12 @@
exports.isatty = function(){
return true;
};
exports.getWindowSize = function(){
if ('innerHeight' in global) {
return [global.innerHeight, global.innerWidth];
} else {
// In a Web Worker, the DOM Window is not available.
return [640, 480];
}
};

View File

@ -0,0 +1,87 @@
// A formatter is a Duplex stream that TAP data is written into,
// and then something else (presumably not-TAP) is read from.
//
// See tap-classic.js for an example of a formatter in use.
var Duplex = require('stream').Duplex
if (!Duplex) {
try {
Duplex = require('readable-stream').Duplex
} catch (er) {
throw new Error('Please install "readable-stream" to use this module ' +
'with Node.js v0.8 and before')
}
}
var util = require('util')
var Parser = require('tap-parser')
util.inherits(Formatter, Duplex)
module.exports = Formatter
function Formatter(options, parser, parent) {
if (!(this instanceof Formatter))
return new Formatter(options, parser, parent)
if (!parser)
parser = new Parser()
Duplex.call(this, options)
this.child = null
this.parent = parent || null
this.level = parser.level
this.parser = parser
attachEvents(this, parser, options)
if (options.init)
options.init.call(this)
}
function attachEvents (self, parser, options) {
var events = [
'version', 'plan', 'assert', 'comment',
'complete', 'extra', 'bailout'
]
parser.on('child', function (childparser) {
self.child = new Formatter(options, childparser, self)
if (options.child)
options.child.call(self, self.child)
})
events.forEach(function (ev) {
if (typeof options[ev] === 'function')
parser.on(ev, options[ev].bind(self))
})
// proxy all stream events directly
var streamEvents = [
'pipe', 'prefinish', 'finish', 'unpipe', 'close'
]
streamEvents.forEach(function (ev) {
parser.on(ev, function () {
var args = [ev]
args.push.apply(args, arguments)
self.emit.apply(self, args)
})
})
}
Formatter.prototype.write = function (c, e, cb) {
return this.parser.write(c, e, cb)
}
Formatter.prototype.end = function (c, e, cb) {
return this.parser.end(c, e, cb)
}
Formatter.prototype._read = function () {}
// child formatters always push data to the root obj
Formatter.prototype.push = function (c) {
if (this.parent)
return this.parent.push(c)
Duplex.prototype.push.call(this, c)
}

View File

@ -0,0 +1,109 @@
/**
* Helpers.
*/
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var y = d * 365.25;
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} options
* @return {String|Number}
* @api public
*/
module.exports = function(val, options){
options = options || {};
if ('string' == typeof val) return parse(val);
return options['long'] ? longFormat(val) : shortFormat(val);
};
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
function parse(str) {
var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
if (!match) return;
var n = parseFloat(match[1]);
var type = (match[2] || 'ms').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'y':
return n * y;
case 'days':
case 'day':
case 'd':
return n * d;
case 'hours':
case 'hour':
case 'h':
return n * h;
case 'minutes':
case 'minute':
case 'm':
return n * m;
case 'seconds':
case 'second':
case 's':
return n * s;
case 'ms':
return n;
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function shortFormat(ms) {
if (ms >= d) return Math.round(ms / d) + 'd';
if (ms >= h) return Math.round(ms / h) + 'h';
if (ms >= m) return Math.round(ms / m) + 'm';
if (ms >= s) return Math.round(ms / s) + 's';
return ms + 'ms';
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function longFormat(ms) {
return plural(ms, d, 'day')
|| plural(ms, h, 'hour')
|| plural(ms, m, 'minute')
|| plural(ms, s, 'second')
|| ms + ' ms';
}
/**
* Pluralization helper.
*/
function plural(ms, n, name) {
if (ms < n) return;
if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
return Math.ceil(ms / n) + ' ' + name + 's';
}

View File

@ -0,0 +1,463 @@
/**
* Module dependencies.
*/
var tty = require('tty')
, diff = require('diff')
, ms = require('../ms')
, utils = require('../utils')
, supportsColor = require('color-support')()
/**
* 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);
/**
* Expose `Base`.
*/
exports = module.exports = Base;
/**
* Enable coloring by default, except in the browser interface.
*/
exports.useColors = process.env
? (supportsColor || (process.env.TAP_COLORS !== undefined))
: false;
if (exports.useColors && +process.env.TAP_COLORS === 0)
exports.useColors = 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': 35
, 'skip': 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
var 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 = utils.stackTraceFilter()(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;
if (!stats.duration)
stats.duration = stats.end - 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);
}
};
/**
* 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;
}

View File

@ -0,0 +1,408 @@
exports = module.exports = Classic
var Base = require('./base')
, cursor = Base.cursor
, color = Base.color
, yaml = require('js-yaml')
, util = require('util')
, fancy = Base.useColors && !process.env.TRAVIS
, ms = require('../ms.js')
, diff = require('diff')
, utils = require('../utils.js')
, uclen = require('unicode-length').get
, colorSupport = require('color-support')()
function repeat (n, c) {
return new Array(Math.max(n + 1, 0)).join(c)
}
function hasOwnProperty (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
function doDiff (found, wanted, palette) {
// TODO: Make this a configurable thing or something?
//
// Choosing a palette for diffs in a test-runner context
// is really tricky. The temptation is to make it look
// exactly like `git diff`, but the default red and green
// are very confusing with the colors used to indicate
// pass/fail.
//
// So, I decided to experiment with setting a background to
// distinguish the diff section from the rest of the test
// output. The obvious choice, then, was to mimick GitHub's
// diff styling.
//
// The problem there is that, while those of us with an
// abundance of cones tend to find that palette most pleasing,
// it's virtually impossible for people with various sorts of
// red/green colorblindness to distinguish those colors.
//
// The resulting option, with a somewhat pink-ish red and a
// somewhat yellow-ish green, seems to be a pretty good
// compromise between esthetics and accessibility. In a poll
// on twitter, it was the only one that no color-sighted people
// strongly objected to, and no color-blind people had
// significant trouble interpreting. The twitter poll agrees
// with the results of Sim Daltonism, which showed that this
// palette was easily distinguishable across all forms of color
// deficiency.
// TODO: add a TrueColor one that looks nicer
palette = colorSupport.has256 ? 6 : 1
var plain = ''
var reset = '\u001b[m'
switch (palette) {
case 1:
// most git-like. r/g, no background, no bold
var bg = ''
var removed = '\u001b[31m'
var added = '\u001b[32m'
break
case 2:
// dark option, maybe confusing with pass/fail colors?
var bg = '\u001b[48;5;234m'
var removed = '\u001b[31;1m'
var added = '\u001b[32;1m'
break
case 3:
// pastel option, most githubby
var removed = '\u001b[48;5;224m\u001b[38;5;52m'
var added = '\u001b[48;5;194m\u001b[38;5;22m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
case 4:
// orange/cyan pastel option, most r/g-colorblind friendly
var removed = '\u001b[48;5;223m\u001b[38;5;52m'
var added = '\u001b[48;5;158m\u001b[38;5;22m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
case 5:
// pastel option, green is bluish, red is just red
var removed = '\u001b[48;5;224m\u001b[38;5;52m'
var added = '\u001b[48;5;158m\u001b[38;5;22m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
case 6:
// pastel option, red is purplish, green is yellowish
var removed = '\u001b[48;5;218m\u001b[38;5;52m'
var added = '\u001b[48;5;193m\u001b[38;5;22m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
case 7:
// pastel option, red is purplish, green is just green
var removed = '\u001b[48;5;218m\u001b[38;5;52m'
var added = '\u001b[48;5;194m\u001b[38;5;22m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
case 8:
// pastel, red and blue
var removed = '\u001b[48;5;224m\u001b[38;5;52m'
var added = '\u001b[48;5;189m\u001b[38;5;17m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
case 9:
// pastel option, red is purplish, green is yellowish
var removed = '\u001b[48;5;224m\u001b[38;5;52m'
var added = '\u001b[48;5;193m\u001b[38;5;22m'
var plain = '\u001b[38;5;233m'
var bg = '\u001b[48;5;255m'
break
}
var maxLen = process.stdout.columns || 0
if (maxLen >= 5)
maxLen -= 5
if (!Base.useColors) {
bg = removed = added = reset = plain = ''
maxLen = 0
}
// If they are not strings, or only differ in trailing whitespace,
// then stringify them so that we can see the difference.
if (typeof found !== 'string' ||
typeof wanted !== 'string' ||
found.trim() === wanted.trim()) {
found = utils.stringify(found)
wanted = utils.stringify(wanted)
}
var patch = diff.createPatch('', wanted, found)
//console.error(patch)
var width = 0
patch = patch.split('\n').map(function (line, index) {
if (uclen(line) > width)
width = Math.min(maxLen, uclen(line))
if (line.match(/^\=+$/) ||
line === '\\ No newline at end of file')
return null
else
return line
}).filter(function (line, i) {
return line && i > 4
}).map(function (line) {
if (uclen(line) < width)
line += repeat(width - uclen(line) + 1, ' ')
return line
}).map(function (line) {
if (line.charAt(0) === '+')
return bg + added + line + reset
else if (line.charAt(0) === '-')
return bg + removed + line + reset
else
return bg + plain + line + reset
}).join('\n')
var pref =
bg + added + '+++ found' +
(Base.useColors
? repeat(width - '+++ found'.length + 1, ' ')
: '') +
reset + '\n' +
bg + removed + '--- wanted' +
(Base.useColors
? repeat(width - '--- wanted'.length + 1, ' ')
: '') +
reset + '\n'
return pref + patch
}
util.inherits(Classic, Base)
function Classic (runner) {
Base.call(this, runner);
var self = this
var grandTotal = 0
var grandPass = 0
var bailed = false
var hadFails = false
var currentSuite = null
var tests = []
var skipped = 0
var skipMsg = []
var todo = []
var fails = []
var total = 0
var pass = 0
var tickDots = 0
var tickColor = 'checkmark'
runner.on('bailout', function (bailout, suite) {
if (currentSuite)
runner.emit('suite end', currentSuite)
if (bailed)
return
bailed = true
console.log(Base.color('fail', 'Bail out! ' + bailout))
})
runner.on('suite', function (suite) {
if (!suite.root)
return
if (fancy) {
process.stdout.write(suite.title + ' ')
tickDots = 0
tickColor = 'checkmark'
}
currentSuite = suite
tests = []
todo = []
fails = []
skipMsg = []
skipped = 0
pass = 0
total = 0
})
runner.on('suite end', function (suite) {
if (!suite.root)
return
if (fancy)
Base.cursor.beginningOfLine()
currentSuite = null
var len = 60
var title = suite.title || '(unnamed)'
var num = pass + '/' + total
var dots = len - uclen(title) - uclen(num) - 3
if (dots < 2)
dots = 2
dots = ' ' + repeat(dots, '.') + ' '
if (fails.length)
num = Base.color('fail', num)
else if (pass === total)
num = Base.color('checkmark', num)
else
num = Base.color('pending', num)
var fmt = title + dots + num
if (suite.duration / total > 250)
fmt += Base.color('slow', ' ' + ms(Math.round(suite.duration)))
console.log(fmt)
if (fails.length) {
var failMsg = ''
fails.forEach(function (t) {
if (t.parent)
failMsg += t.parent + '\n'
failMsg += Base.color('fail', 'not ok ' + t.name) + '\n'
if (t.diag) {
var printDiff = false
if (hasOwnProperty(t.diag, 'found') &&
hasOwnProperty(t.diag, 'wanted')) {
printDiff = true
var found = t.diag.found
var wanted = t.diag.wanted
failMsg += indent(doDiff(found, wanted), 2) + '\n'
}
var o = {}
var print = false
for (var i in t.diag) {
// Don't re-print what we already showed in the diff
if (printDiff && ( i === 'found' || i === 'wanted'))
continue
o[i] = t.diag[i]
print = true
}
if (print)
failMsg += indent(yaml.safeDump(o), 2) + '\n'
}
})
console.log(indent(failMsg, 2))
}
if (todo.length) {
var todoMsg = ''
var bullet = Base.color('pending', '~ ')
todo.forEach(function (t) {
if (t.todo !== true)
t.name += ' - ' + Base.color('pending', t.todo)
todoMsg += bullet + t.name + '\n'
if (t.diag)
todoMsg += indent(yaml.safeDump(t.diag), 4) + '\n'
})
console.log(indent(todoMsg, 2))
}
if (skipped) {
var fmt = Base.color('skip', indent('Skipped: %d', 2))
console.log(fmt, skipped)
if (skipMsg.length)
console.log(indent(skipMsg.join('\n'), 4))
console.log('')
}
})
runner.on('test', function (test) {
total ++
grandTotal ++
var t = test.result
if (fancy && currentSuite) {
var max = 57 - uclen(currentSuite.title)
if (max < 3)
max = 3
if (tickDots > max) {
tickDots = 0
Base.cursor.deleteLine()
Base.cursor.beginningOfLine();
process.stdout.write(currentSuite.title + ' ')
}
tickDots ++
if (t.todo &&
(tickColor === 'checkmark' || tickColor === 'skip'))
tickColor = 'pending'
else if (t.skip && tickColor === 'checkmark')
tickColor = 'skip'
else if (!t.ok)
tickColor = 'fail'
process.stdout.write(Base.color(tickColor, '.'))
}
if (t.skip) {
skipped += 1
if (!/^filter(( out)?: \/.+\/|: only)$/.test(t.skip)) {
if (t.skip !== true)
skipMsg.push(t.name + ' ' + Base.color('skip', t.skip))
else
skipMsg.push(t.name)
}
}
else if (t.todo)
todo.push(t)
else if (!t.ok) {
t.parent = []
var p = test.parent
while (p && p !== currentSuite) {
var n = p.title || p.name || p.fullTitle()
if (n)
t.parent.unshift(n)
p = p.parent
}
t.parent.shift()
t.parent = t.parent.join(' > ')
fails.push(t)
hadFails = true
}
else {
pass ++
grandPass ++
}
})
runner.on('end', function () {
total = grandTotal
pass = grandPass
tests = []
todo = []
fails = []
skipMsg = []
skipped = 0
if (hadFails)
fails = [,,,]
runner.emit('suite end', { title: 'total', root: true })
self.failures = []
self.epilogue();
if (grandTotal === grandPass) {
console.log(Base.color('checkmark', '\n ok'))
}
})
}
function indent (str, n) {
var ind = repeat(n, ' ')
str = ind + str.split('\n').join('\n' + ind)
return str.replace(/(\n\s*)+$/, '\n')
}

View File

@ -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));
});
}

View File

@ -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;

View File

@ -0,0 +1,50 @@
exports = module.exports = Dump
var Base = require('./base')
, cursor = Base.cursor
, color = Base.color
, useColors = Base.useColors
, util = require('util')
function Dump(runner) {
Base.call(this, runner);
var events = [
'start',
'version',
'suite',
'suite end',
'test',
'pending',
'pass',
'fail',
'test end',
];
var i = process.argv.indexOf('dump')
if (i !== -1) {
var args = process.argv.slice(i + 1)
if (args.length)
events = args
}
runner.on('line', function (c) {
if (c.trim())
process.stderr.write(Base.color('bright yellow', c))
})
events.forEach(function (ev) {
runner.on(ev, function (obj) {
console.log(ev)
if (arguments.length) {
console.log(util.inspect(obj, false, Infinity, useColors))
console.log()
}
})
})
runner.on('end', function () {
console.log('end')
console.log(runner.stats)
console.log()
})
}

View File

@ -0,0 +1,16 @@
exports.dot = require('./dot.js')
exports.doc = require('./doc.js')
exports.tap = true
exports.json = require('./json.js')
exports.list = require('./list.js')
exports.min = require('./min.js')
exports.spec = require('./spec.js')
exports.nyan = require('./nyan.js')
exports.xunit = require('./xunit.js')
exports.markdown = require('./markdown.js')
exports.progress = require('./progress.js')
exports.landing = require('./landing.js')
exports.jsonstream = require('./json-stream.js')
exports.dump = require('./dump.js')
exports.classic = require('./classic.js')
exports.silent = require('./silent.js')

View File

@ -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
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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')
+ (test.duration ? color('pass', ': ') + 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;

View File

@ -0,0 +1,126 @@
/**
* 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 + 1).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() {
return suites.map(generateTOC_).join('')
}
function generateTOC_(suite) {
var obj = mapTOC(suite, {});
return stringifyTOC(obj, 0);
}
var suites = []
var currentSuite = null
runner.on('suite', function(suite){
currentSuite = suite
if (suite.root) {
suites.push(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){
if (suite.ok) {
buf += '\nok - ' + suite.title + '\n'
} else {
buf += '\nnot ok - ' + suite.title + '\n'
}
--level;
});
runner.on('test', function(test){
if (!test.ok || test.pending) {
var code = utils.clean(test.fn.toString());
buf += test.title + '.\n';
if (code) {
buf += '\n```js\n';
buf += code + '\n';
buf += '```\n';
}
var stack = test.err && test.err.stack
if (!stack) {
stack = test.result && test.result.diag && test.result.diag.stack
}
if (stack) {
buf += '\n```\n' + stack + '\n```\n';
}
buf += '\n\n';
}
});
runner.on('end', function(){
process.stdout.write('# TOC\n');
process.stdout.write(generateTOC());
process.stdout.write('\n\n');
process.stdout.write(buf);
});
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1 @@
exports = module.exports = function () {}

View File

@ -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;

View File

@ -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 || ' '

View File

@ -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

View File

@ -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>

File diff suppressed because one or more lines are too long

View File

@ -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: 'TAP 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) + ']]>';
}

View File

@ -0,0 +1,347 @@
// A facade from the tap-parser to the Mocha "Runner" object.
// Note that pass/fail/suite events need to also mock the "Runnable"
// objects (either "Suite" or "Test") since these have functions
// which are called by the formatters.
module.exports = Runner
// relevant events:
//
// start()
// Start of the top-level test set
//
// end()
// End of the top-level test set.
//
// fail(test, err)
// any "not ok" test that is not the trailing test for a suite
// of >0 test points.
//
// pass(test)
// any "ok" test point that is not the trailing test for a suite
// of >0 tests
//
// pending(test)
// Any "todo" test
//
// suite(suite)
// A suite is a child test with >0 test points. This is a little bit
// tricky, because TAP will provide a "child" event before we know
// that it's a "suite". We see the "# Subtest: name" comment as the
// first thing in the subtest. Then, when we get our first test point,
// we know that it's a suite, and can emit the event with the mock suite.
//
// suite end(suite)
// Emitted when we end the subtest
//
// test(test)
// Any test point which is not the trailing test for a suite.
//
// test end(test)
// Emitted immediately after the "test" event because test points are
// not async in TAP.
var util = require('util')
var Test = require('./test.js')
var Suite = require('./suite.js')
var Writable = require('stream').Writable
if (!Writable) {
try {
Writable = require('readable-stream').Writable
} catch (er) {
throw new Error('Please install "readable-stream" to use this module ' +
'with Node.js v0.8 and before')
}
}
var Parser = require('tap-parser')
// $1 = number, $2 = units
var timere = /^#\s*time=((?:0|[1-9][0-9]*?)(?:\.[0-9]+)?)(ms|s)?$/
util.inherits(Runner, Writable)
function Runner (options) {
if (!(this instanceof Runner))
return new Runner(options)
var parser = this.parser = new Parser(options)
this.startTime = new Date()
attachEvents(this, parser, 0)
Writable.call(this, options)
}
Runner.prototype.write = function () {
if (!this.emittedStart) {
this.emittedStart = true
this.emit('start')
}
return this.parser.write.apply(this.parser, arguments)
}
Runner.prototype.end = function () {
return this.parser.end.apply(this.parser, arguments)
}
Parser.prototype.fullTitle = function () {
if (!this.parent)
return this.name || ''
else
return (this.parent.fullTitle() + ' ' + (this.name || '')).trim()
}
function attachEvents (runner, parser, level) {
parser.runner = runner
if (level === 0) {
parser.on('line', function (c) {
runner.emit('line', c)
})
parser.on('version', function (v) {
runner.emit('version', v)
})
parser.on('complete', function (res) {
runner.emit('end')
})
parser.on('comment', function (c) {
var tmatch = c.trim().match(timere)
if (tmatch) {
var t = +tmatch[1]
if (tmatch[2] === 's')
t *= 1000
parser.time = t
if (runner.stats)
runner.stats.duration = t
}
})
}
parser.emittedSuite = false
parser.didAssert = false
parser.name = parser.name || ''
parser.doingChild = null
parser.on('complete', function (res) {
if (!res.ok) {
var fail = { ok: false, diag: {} }
var count = res.count
if (res.plan) {
var plan = res.plan.end - res.plan.start + 1
if (count !== plan) {
fail.name = 'test count !== plan'
fail.diag = {
found: count,
wanted: plan
}
} else {
// probably handled on child parser
return
}
} else {
fail.name = 'missing plan'
}
fail.diag.results = res
emitTest(parser, fail)
}
})
parser.on('child', function (child) {
child.parent = parser
attachEvents(runner, child, level + 1)
// if we're in a suite, but we haven't emitted it yet, then we
// know that an assert will follow this child, even if there are
// no others. That means that we will definitely have a 'suite'
// event to emit.
emitSuite(this)
this.didAssert = true
this.doingChild = child
})
if (!parser.name) {
parser.on('comment', function (c) {
if (!this.name && c.match(/^# Subtest: /)) {
c = c.trim().replace(/^# Subtest: /, '')
this.name = c
}
})
}
// Just dump all non-parsing stuff to stderr
parser.on('extra', function (c) {
process.stderr.write(c)
})
parser.on('assert', function (result) {
emitSuite(this)
// no need to print the trailing assert for subtests
// we've already emitted a 'suite end' event for this.
// UNLESS, there were no other asserts, AND it's root level
if (this.doingChild) {
var suite = this.doingChild.suite
if (this.doingChild.name === result.name) {
if (suite) {
if (result.time)
suite.duration = result.time
// If it's ok so far, but the ending result is not-ok, then
// that means that it exited non-zero. Emit the test so
// that we can print it as a failure.
if (suite.ok && !result.ok)
emitTest(this, result)
}
}
var emitOn = this
var dc = this.doingChild
this.doingChild = null
if (!dc.didAssert && dc.level === 1) {
emitOn = dc
} else if (dc.didAssert) {
if (dc.suite)
runner.emit('suite end', dc.suite)
return
} else {
emitOn = this
}
emitSuite(emitOn)
emitTest(emitOn, result)
if (emitOn !== this && emitOn.suite) {
runner.emit('suite end', emitOn.suite)
delete emitOn.suite
}
if (dc.suite) {
runner.emit('suite end', dc.suite)
}
return
}
this.didAssert = true
this.doingChild = null
emitTest(this, result)
})
parser.on('complete', function (results) {
this.results = results
})
parser.on('bailout', function (reason) {
var suite = this.suite
runner.emit('bailout', reason, suite)
if (suite)
this.suite = suite.parent
})
// proxy all stream events directly
var streamEvents = [
'pipe', 'prefinish', 'finish', 'unpipe', 'close'
]
streamEvents.forEach(function (ev) {
parser.on(ev, function () {
var args = [ev]
args.push.apply(args, arguments)
runner.emit.apply(runner, args)
})
})
}
function emitSuite (parser) {
if (!parser.emittedSuite && parser.name) {
parser.emittedSuite = true
var suite = parser.suite = new Suite(parser)
if (parser.parent && parser.parent.suite)
parser.parent.suite.suites.push(suite)
if (parser.runner.stats)
parser.runner.stats.suites ++
parser.runner.emit('suite', suite)
}
}
function emitTest (parser, result) {
var runner = parser.runner
var test = new Test(result, parser)
if (parser.suite) {
parser.suite.tests.push(test)
if (!result.ok) {
for (var p = parser; p && p.suite; p = p.parent) {
p.suite.ok = false
}
}
parser.suite.ok = parser.suite.ok && result.ok
}
runner.emit('test', test)
if (result.skip || result.todo) {
runner.emit('pending', test)
} else if (result.ok) {
runner.emit('pass', test)
} else {
var error = getError(result)
runner.emit('fail', test, error)
}
runner.emit('test end', test)
}
function getError (result) {
var err
function reviveStack (stack) {
if (!stack)
return null
return stack.trim().split('\n').map(function (line) {
return ' at ' + line
}).join('\n')
}
if (result.diag && result.diag.error) {
err = {
name: result.diag.error.name || 'Error',
message: result.diag.error.message,
toString: function () {
return this.name + ': ' + this.message
},
stack: result.diag.error.stack
}
} else {
err = {
message: (result.name || '(unnamed error)').replace(/^Error: /, ''),
toString: function () {
return 'Error: ' + this.message
},
stack: result.diag && result.diag.stack
}
}
var diag = result.diag
if (err.stack)
err.stack = err.toString() + '\n' + reviveStack(err.stack)
if (diag) {
var hasFound = diag.hasOwnProperty('found')
var hasWanted = diag.hasOwnProperty('wanted')
if (hasFound)
err.actual = diag.found
if (hasWanted)
err.expected = diag.wanted
if (hasFound && hasWanted)
err.showDiff = true
}
return err
}

View File

@ -0,0 +1,22 @@
// minimal mock of mocha's Suite class for formatters
module.exports = Suite
function Suite (parent) {
if (!parent.parent || !parent.parent.emittedSuite)
this.root = true
else
this.root = false
this.title = parent.name || ''
this.suites = []
this.tests = []
this.ok = true
}
Suite.prototype.fullTitle = function () {
if (!this.parent)
return (this.title || '').trim()
else
return (this.parent.fullTitle() + ' ' + (this.title || '')).trim()
}

View File

@ -0,0 +1,41 @@
// minimal mock of the mocha Test class for formatters
module.exports = Test
function Test (result, parent) {
this.result = result
this._slow = 75
this.duration = result.time
this.title = result.name
this.state = result.ok ? 'pass' : 'failed'
this.pending = result.todo || result.skip || false
if (result.diag && result.diag.source) {
var source = result.diag.source
this.fn = {
toString: function () {
return 'function(){' + source + '\n}'
}
}
}
Object.defineProperty(this, 'parent', {
value: parent,
writable: true,
configurable: true,
enumerable: false
})
}
Test.prototype.fullTitle = function () {
return (this.parent.fullTitle() + ' ' + (this.title || '')).trim()
}
Test.prototype.slow = function (ms){
return 75
}
Test.prototype.fn = {
toString: function () {
return 'function () {\n}'
}
}

View File

@ -0,0 +1,699 @@
/**
* Module dependencies.
*/
var fs = require('fs')
, path = require('path')
, basename = path.basename
, exists = fs.existsSync || path.existsSync
, glob = require('glob')
, join = path.join
, debug = require('debug')('mocha:watch');
/**
* Ignored directories.
*/
var ignore = ['node_modules', '.git'];
/**
* Escape special characters in the given string of html.
*
* @param {String} html
* @return {String}
* @api private
*/
exports.escape = function(html){
return String(html)
.replace('&', '&amp;')
.replace('"', '&quot;')
.replace('<', '&lt;')
.replace('>', '&gt;');
};
/**
* Array#forEach (<=IE8)
*
* @param {Array} array
* @param {Function} fn
* @param {Object} scope
* @api private
*/
exports.forEach = function(arr, fn, scope){
for (var i = 0, l = arr.length; i < l; i++)
fn.call(scope, arr[i], i);
};
/**
* Array#map (<=IE8)
*
* @param {Array} array
* @param {Function} fn
* @param {Object} scope
* @api private
*/
exports.map = function(arr, fn, scope){
var result = [];
for (var i = 0, l = arr.length; i < l; i++)
result.push(fn.call(scope, arr[i], i, arr));
return result;
};
/**
* Array#indexOf (<=IE8)
*
* @parma {Array} arr
* @param {Object} obj to find index of
* @param {Number} start
* @api private
*/
exports.indexOf = function(arr, obj, start){
for (var i = start || 0, l = arr.length; i < l; i++) {
if (arr[i] === obj)
return i;
}
return -1;
};
/**
* Array#reduce (<=IE8)
*
* @param {Array} array
* @param {Function} fn
* @param {Object} initial value
* @api private
*/
exports.reduce = function(arr, fn, val){
var rval = val;
for (var i = 0, l = arr.length; i < l; i++) {
rval = fn(rval, arr[i], i, arr);
}
return rval;
};
/**
* Array#filter (<=IE8)
*
* @param {Array} array
* @param {Function} fn
* @api private
*/
exports.filter = function(arr, fn){
var ret = [];
for (var i = 0, l = arr.length; i < l; i++) {
var val = arr[i];
if (fn(val, i, arr)) ret.push(val);
}
return ret;
};
/**
* Object.keys (<=IE8)
*
* @param {Object} obj
* @return {Array} keys
* @api private
*/
exports.keys = Object.keys || function(obj) {
var keys = []
, has = Object.prototype.hasOwnProperty; // for `window` on <=IE8
for (var key in obj) {
if (has.call(obj, key)) {
keys.push(key);
}
}
return keys;
};
/**
* Watch the given `files` for changes
* and invoke `fn(file)` on modification.
*
* @param {Array} files
* @param {Function} fn
* @api private
*/
exports.watch = function(files, fn){
var options = { interval: 100 };
files.forEach(function(file){
debug('file %s', file);
fs.watchFile(file, options, function(curr, prev){
if (prev.mtime < curr.mtime) fn(file);
});
});
};
/**
* Array.isArray (<=IE8)
*
* @param {Object} obj
* @return {Boolean}
* @api private
*/
var isArray = Array.isArray || function (obj) {
return '[object Array]' == {}.toString.call(obj);
};
/**
* @description
* Buffer.prototype.toJSON polyfill
* @type {Function}
*/
if(typeof Buffer !== 'undefined' && Buffer.prototype) {
Buffer.prototype.toJSON = Buffer.prototype.toJSON || function () {
return Array.prototype.slice.call(this, 0);
};
}
/**
* Ignored files.
*/
function ignored(path){
return !~ignore.indexOf(path);
}
/**
* Lookup files in the given `dir`.
*
* @return {Array}
* @api private
*/
exports.files = function(dir, ext, ret){
ret = ret || [];
ext = ext || ['js'];
var re = new RegExp('\\.(' + ext.join('|') + ')$');
fs.readdirSync(dir)
.filter(ignored)
.forEach(function(path){
path = join(dir, path);
if (fs.statSync(path).isDirectory()) {
exports.files(path, ext, ret);
} else if (path.match(re)) {
ret.push(path);
}
});
return ret;
};
/**
* Compute a slug from the given `str`.
*
* @param {String} str
* @return {String}
* @api private
*/
exports.slug = function(str){
return str
.toLowerCase()
.replace(/ +/g, '-')
.replace(/[^-\w]/g, '');
};
/**
* Strip the function definition from `str`,
* and re-indent for pre whitespace.
*/
exports.clean = function(str) {
str = str
.replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '')
.replace(/^function *\(.*\) *{|\(.*\) *=> *{?/, '')
.replace(/\s+\}$/, '');
var spaces = str.match(/^\n?( *)/)[1].length
, tabs = str.match(/^\n?(\t*)/)[1].length
, re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm');
str = str.replace(re, '');
return exports.trim(str);
};
/**
* Trim the given `str`.
*
* @param {String} str
* @return {String}
* @api private
*/
exports.trim = function(str){
return str.replace(/^\s+|\s+$/g, '');
};
/**
* Parse the given `qs`.
*
* @param {String} qs
* @return {Object}
* @api private
*/
exports.parseQuery = function(qs){
return exports.reduce(qs.replace('?', '').split('&'), function(obj, pair){
var i = pair.indexOf('=')
, key = pair.slice(0, i)
, val = pair.slice(++i);
obj[key] = decodeURIComponent(val);
return obj;
}, {});
};
/**
* Highlight the given string of `js`.
*
* @param {String} js
* @return {String}
* @api private
*/
function highlight(js) {
return js
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
.replace(/('.*?')/gm, '<span class="string">$1</span>')
.replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
.replace(/(\d+)/gm, '<span class="number">$1</span>')
.replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
.replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>')
}
/**
* Highlight the contents of tag `name`.
*
* @param {String} name
* @api private
*/
exports.highlightTags = function(name) {
var code = document.getElementById('mocha').getElementsByTagName(name);
for (var i = 0, len = code.length; i < len; ++i) {
code[i].innerHTML = highlight(code[i].innerHTML);
}
};
/**
* If a value could have properties, and has none, this function is called, which returns
* a string representation of the empty value.
*
* Functions w/ no properties return `'[Function]'`
* Arrays w/ length === 0 return `'[]'`
* Objects w/ no properties return `'{}'`
* All else: return result of `value.toString()`
*
* @param {*} value Value to inspect
* @param {string} [type] The type of the value, if known.
* @returns {string}
*/
var emptyRepresentation = function emptyRepresentation(value, type) {
type = type || exports.type(value);
switch(type) {
case 'function':
return '[Function]';
case 'object':
return '{}';
case 'array':
return '[]';
default:
return value.toString();
}
};
/**
* Takes some variable and asks `{}.toString()` what it thinks it is.
* @param {*} value Anything
* @example
* type({}) // 'object'
* type([]) // 'array'
* type(1) // 'number'
* type(false) // 'boolean'
* type(Infinity) // 'number'
* type(null) // 'null'
* type(new Date()) // 'date'
* type(/foo/) // 'regexp'
* type('type') // 'string'
* type(global) // 'global'
* @api private
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
* @returns {string}
*/
exports.type = function type(value) {
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
return 'buffer';
}
return Object.prototype.toString.call(value)
.replace(/^\[.+\s(.+?)\]$/, '$1')
.toLowerCase();
};
/**
* @summary Stringify `value`.
* @description Different behavior depending on type of value.
* - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively.
* - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes.
* - If `value` is an *empty* object, function, or array, return result of function
* {@link emptyRepresentation}.
* - If `value` has properties, call {@link exports.canonicalize} on it, then return result of
* JSON.stringify().
*
* @see exports.type
* @param {*} value
* @return {string}
* @api private
*/
exports.stringify = function(value) {
var type = exports.type(value);
if (!~exports.indexOf(['object', 'array', 'function'], type)) {
if(type != 'buffer') {
return jsonStringify(value);
}
var json = value.toJSON();
// Based on the toJSON result
return jsonStringify(json.data && json.type ? json.data : json, 2)
.replace(/,(\n|$)/g, '$1');
}
for (var prop in value) {
if (Object.prototype.hasOwnProperty.call(value, prop)) {
return jsonStringify(exports.canonicalize(value), 2)
.replace(/,(\n|$)/g, '$1');
}
}
return emptyRepresentation(value, type);
};
/**
* @description
* like JSON.stringify but more sense.
* @param {Object} object
* @param {Number=} spaces
* @param {number=} depth
* @returns {*}
* @private
*/
function jsonStringify(object, spaces, depth) {
if(typeof spaces == 'undefined') return _stringify(object); // primitive types
depth = depth || 1;
var space = spaces * depth
, str = isArray(object) ? '[' : '{'
, end = isArray(object) ? ']' : '}'
, length = object.length || exports.keys(object).length
, repeat = function(s, n) { return new Array(n).join(s); }; // `.repeat()` polyfill
function _stringify(val) {
switch (exports.type(val)) {
case 'null':
case 'undefined':
val = '[' + val + ']';
break;
case 'array':
case 'object':
val = jsonStringify(val, spaces, depth + 1);
break;
case 'boolean':
case 'regexp':
case 'number':
val = val === 0 && (1/val) === -Infinity // `-0`
? '-0'
: val.toString();
break;
case 'date':
val = '[Date: ' + val.toISOString() + ']';
break;
case 'buffer':
var json = val.toJSON();
// Based on the toJSON result
json = json.data && json.type ? json.data : json;
val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']';
break;
default:
val = (val == '[Function]' || val == '[Circular]')
? val
: '"' + val + '"'; //string
}
return val;
}
for(var i in object) {
if(!Object.prototype.hasOwnProperty.call(object, i)) continue; // not my business
--length;
str += '\n ' + repeat(' ', space)
+ (isArray(object) ? '' : '"' + i + '": ') // key
+ _stringify(object[i]) // value
+ (length ? ',' : ''); // comma
}
return str + (str.length != 1 // [], {}
? '\n' + repeat(' ', --space) + end
: end);
}
/**
* Return if obj is a Buffer
* @param {Object} arg
* @return {Boolean}
* @api private
*/
exports.isBuffer = function (arg) {
return typeof Buffer !== 'undefined' && Buffer.isBuffer(arg);
};
/**
* @summary Return a new Thing that has the keys in sorted order. Recursive.
* @description If the Thing...
* - has already been seen, return string `'[Circular]'`
* - is `undefined`, return string `'[undefined]'`
* - is `null`, return value `null`
* - is some other primitive, return the value
* - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method
* - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again.
* - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()`
*
* @param {*} value Thing to inspect. May or may not have properties.
* @param {Array} [stack=[]] Stack of seen values
* @return {(Object|Array|Function|string|undefined)}
* @see {@link exports.stringify}
* @api private
*/
exports.canonicalize = function(value, stack) {
var canonicalizedObj,
type = exports.type(value),
prop,
withStack = function withStack(value, fn) {
stack.push(value);
fn();
stack.pop();
};
stack = stack || [];
if (exports.indexOf(stack, value) !== -1) {
return '[Circular]';
}
switch(type) {
case 'undefined':
case 'buffer':
case 'null':
canonicalizedObj = value;
break;
case 'array':
withStack(value, function () {
canonicalizedObj = exports.map(value, function (item) {
return exports.canonicalize(item, stack);
});
});
break;
case 'function':
for (prop in value) {
canonicalizedObj = {};
break;
}
if (!canonicalizedObj) {
canonicalizedObj = emptyRepresentation(value, type);
break;
}
/* falls through */
case 'object':
canonicalizedObj = canonicalizedObj || {};
withStack(value, function () {
exports.forEach(exports.keys(value).sort(), function (key) {
canonicalizedObj[key] = exports.canonicalize(value[key], stack);
});
});
break;
case 'date':
case 'number':
case 'regexp':
case 'boolean':
canonicalizedObj = value;
break;
default:
canonicalizedObj = value.toString();
}
return canonicalizedObj;
};
/**
* Lookup file names at the given `path`.
*/
exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
var files = [];
var re = new RegExp('\\.(' + extensions.join('|') + ')$');
if (!exists(path)) {
if (exists(path + '.js')) {
path += '.js';
} else {
files = glob.sync(path);
if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'");
return files;
}
}
try {
var stat = fs.statSync(path);
if (stat.isFile()) return path;
}
catch (ignored) {
return;
}
fs.readdirSync(path).forEach(function(file) {
file = join(path, file);
try {
var stat = fs.statSync(file);
if (stat.isDirectory()) {
if (recursive) {
files = files.concat(lookupFiles(file, extensions, recursive));
}
return;
}
}
catch (ignored) {
return;
}
if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return;
files.push(file);
});
return files;
};
/**
* Generate an undefined error with a message warning the user.
*
* @return {Error}
*/
exports.undefinedError = function() {
return new Error('Caught undefined error, did you throw without specifying what?');
};
/**
* Generate an undefined error if `err` is not defined.
*
* @param {Error} err
* @return {Error}
*/
exports.getError = function(err) {
return err || exports.undefinedError();
};
/**
* @summary
* This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`)
* @description
* When invoking this function you get a filter function that get the Error.stack as an input,
* and return a prettify output.
* (i.e: strip Mocha, node_modules, bower and componentJS from stack trace).
* @returns {Function}
*/
exports.stackTraceFilter = function() {
var slash = '/'
, is = typeof document === 'undefined'
? { node: true }
: { browser: true }
, cwd = is.node
? process.cwd() + slash
: location.href.replace(/\/[^\/]*$/, '/');
function isNodeModule (line) {
return (~line.indexOf('node_modules'));
}
function isMochaInternal (line) {
return (~line.indexOf('node_modules' + slash + 'tap-mocha-reporter')) ||
(~line.indexOf('components' + slash + 'mochajs')) ||
(~line.indexOf('components' + slash + 'mocha'));
}
// node_modules, bower, componentJS
function isBrowserModule(line) {
return (~line.indexOf('node_modules')) ||
(~line.indexOf('components'));
}
function isNodeInternal (line) {
return (~line.indexOf('(timers.js:')) ||
(~line.indexOf('(domain.js:')) ||
(~line.indexOf('(events.js:')) ||
(~line.indexOf('(node.js:')) ||
(~line.indexOf('(module.js:')) ||
(~line.indexOf('at node.js:')) ||
(~line.indexOf('GeneratorFunctionPrototype.next (native)')) ||
false
}
return function(stack) {
stack = stack.split('\n');
stack = stack.reduce(function (list, line) {
if (is.node && (isNodeModule(line) ||
isMochaInternal(line) ||
isNodeInternal(line)))
return list;
if (is.browser && (isBrowserModule(line)))
return list;
// Clean up cwd(absolute)
list.push(line.replace(cwd, ''));
return list;
}, []);
return stack.join('\n');
}
};