[Pkg-javascript-commits] [node-tap-mocha-reporter] 02/137: stuff mostly working
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 30b92354cb5497d994b7e8916f8330b5ee281bab
Author: isaacs <i at izs.me>
Date: Fri Apr 10 14:47:18 2015 -0700
stuff mostly working
---
index.js | 69 ++++
lib/browser/debug.js | 4 +
lib/browser/escape-string-regexp.js | 11 +
lib/browser/events.js | 177 +++++++++
lib/browser/fs.js | 0
lib/browser/glob.js | 0
lib/browser/path.js | 0
lib/browser/progress.js | 125 +++++++
lib/browser/tty.js | 12 +
lib/formatter.js | 78 ++++
lib/ms.js | 109 ++++++
lib/reporters/base.js | 8 +-
lib/reporters/dump.js | 35 ++
lib/reporters/index.js | 36 +-
lib/reporters/silent.js | 1 +
lib/runner.js | 241 +++++++++++++
lib/suite.js | 21 ++
lib/test.js | 28 ++
lib/utils.js | 698 ++++++++++++++++++++++++++++++++++++
package.json | 24 ++
20 files changed, 1657 insertions(+), 20 deletions(-)
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..a2ccd42
--- /dev/null
+++ b/index.js
@@ -0,0 +1,69 @@
+#!/usr/bin/env node
+
+module.exports = Formatter
+
+var util = require('util')
+var reporters = require('./lib/reporters/index.js')
+var Writable = require('stream').Writable
+var Runner = require('./lib/runner.js')
+
+util.inherits(Formatter, Writable)
+
+function Formatter (type, options) {
+ if (!reporters[type]) {
+ console.error('Unknown format type: %s\n\n%s', type, avail())
+ type = 'silent'
+ }
+
+ var runner = this.runner = new Runner(options)
+ this.reporter = new reporters[type](this.runner)
+ Writable.call(this, options)
+
+ runner.on('end', function () {
+ process.nextTick(function () {
+ if (!runner.parser.ok)
+ process.exit(1)
+ })
+ })
+}
+
+Formatter.prototype.write = function () {
+ return this.runner.write.apply(this.runner, arguments)
+}
+
+Formatter.prototype.end = function () {
+ return this.runner.end.apply(this.runner, arguments)
+}
+
+function avail () {
+ var types = Object.keys(reporters).sort().reduce(function (str, t) {
+ var ll = str.split('\n').pop().length + t.length
+ if (ll < 40)
+ return str + ' ' + t
+ else
+ return str + '\n' + t
+ }, '').trim()
+
+ return 'Available format types:\n\n' + types
+}
+
+
+function usage (err) {
+ console[err ? 'error' : 'log'](function () {/*
+Usage:
+ tap-mocha-reporter <type>
+
+Reads TAP data on stdin, and formats to stdout using the specified
+reporter. (Note that some reporters write to files instead of stdout.)
+
+%s
+*/}.toString().split('\n').slice(1, -1).join('\n'), avail())
+}
+
+if (require.main === module) {
+ var type = process.argv[2]
+ if (!type)
+ return usage()
+
+ process.stdin.pipe(new Formatter(type))
+}
diff --git a/lib/browser/debug.js b/lib/browser/debug.js
new file mode 100644
index 0000000..0d939e5
--- /dev/null
+++ b/lib/browser/debug.js
@@ -0,0 +1,4 @@
+module.exports = function(type){
+ return function(){
+ }
+};
diff --git a/lib/browser/escape-string-regexp.js b/lib/browser/escape-string-regexp.js
new file mode 100644
index 0000000..21a9566
--- /dev/null
+++ b/lib/browser/escape-string-regexp.js
@@ -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, '\\$&');
+};
diff --git a/lib/browser/events.js b/lib/browser/events.js
new file mode 100644
index 0000000..f708260
--- /dev/null
+++ b/lib/browser/events.js
@@ -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;
+};
diff --git a/lib/browser/fs.js b/lib/browser/fs.js
new file mode 100644
index 0000000..e69de29
diff --git a/lib/browser/glob.js b/lib/browser/glob.js
new file mode 100644
index 0000000..e69de29
diff --git a/lib/browser/path.js b/lib/browser/path.js
new file mode 100644
index 0000000..e69de29
diff --git a/lib/browser/progress.js b/lib/browser/progress.js
new file mode 100644
index 0000000..b30e517
--- /dev/null
+++ b/lib/browser/progress.js
@@ -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;
+};
diff --git a/lib/browser/tty.js b/lib/browser/tty.js
new file mode 100644
index 0000000..eab6388
--- /dev/null
+++ b/lib/browser/tty.js
@@ -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];
+ }
+};
diff --git a/lib/formatter.js b/lib/formatter.js
new file mode 100644
index 0000000..91cd89f
--- /dev/null
+++ b/lib/formatter.js
@@ -0,0 +1,78 @@
+// 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
+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)
+}
diff --git a/lib/ms.js b/lib/ms.js
new file mode 100644
index 0000000..ba451fa
--- /dev/null
+++ b/lib/ms.js
@@ -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';
+}
diff --git a/lib/reporters/base.js b/lib/reporters/base.js
index c253c11..a1429ef 100644
--- a/lib/reporters/base.js
+++ b/lib/reporters/base.js
@@ -168,12 +168,14 @@ exports.list = function(failures){
var err = test.err
, message = err.message || ''
, stack = err.stack || message
- , index = stack.indexOf(message) + message.length
+
+ 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;
@@ -199,8 +201,8 @@ exports.list = function(failures){
}
// indent stack trace without msg
- stack = stack.slice(index ? index + 1 : index)
- .replace(/^/gm, ' ');
+ stack = utils.stackTraceFilter()(stack.slice(index ? index + 1 : index)
+ .replace(/^/gm, ' '));
console.log(fmt, (i + 1), test.fullTitle(), msg, stack);
});
diff --git a/lib/reporters/dump.js b/lib/reporters/dump.js
new file mode 100644
index 0000000..10e7e75
--- /dev/null
+++ b/lib/reporters/dump.js
@@ -0,0 +1,35 @@
+exports = module.exports = Dump
+var Base = require('./base')
+ , cursor = Base.cursor
+ , color = Base.color;
+
+function Dump(runner) {
+ Base.call(this, runner);
+
+ var events = [
+ 'start',
+ 'version',
+ 'end',
+ 'suite',
+ 'suite end',
+ 'test',
+ 'pending',
+ 'pass',
+ 'fail',
+ 'test end',
+ ];
+
+ var i = process.argv.indexOf('dump')
+ if (i !== -1) {
+ var args = process.argv.slice(i)
+ if (args.length)
+ events = args
+ }
+
+ events.forEach(function (ev) {
+ runner.on(ev, function () {
+ var args = [].concat.apply([ev], arguments)
+ console.log.apply(console, args)
+ })
+ })
+}
diff --git a/lib/reporters/index.js b/lib/reporters/index.js
index 87b76d9..bdde95b 100644
--- a/lib/reporters/index.js
+++ b/lib/reporters/index.js
@@ -1,17 +1,19 @@
-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');
+exports.base = require('./base.js')
+exports.dot = require('./dot.js')
+exports.doc = require('./doc.js')
+exports.tap = require('./tap.js')
+exports.json = require('./json.js')
+exports.html = require('./html.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.jsoncov = require('./json-cov.js')
+exports.htmlcov = require('./html-cov.js')
+exports.jsonstream = require('./json-stream.js')
+exports.dump = require('./dump.js')
+exports.silent = require('./silent.js')
diff --git a/lib/reporters/silent.js b/lib/reporters/silent.js
new file mode 100644
index 0000000..45548da
--- /dev/null
+++ b/lib/reporters/silent.js
@@ -0,0 +1 @@
+exports = module.exports = function () {}
diff --git a/lib/runner.js b/lib/runner.js
new file mode 100644
index 0000000..87c6756
--- /dev/null
+++ b/lib/runner.js
@@ -0,0 +1,241 @@
+// 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
+var Parser = require('tap-parser')
+
+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) {
+ var events = [
+ 'version', 'plan', 'assert', 'comment',
+ 'complete', 'extra', 'bailout'
+ ]
+
+ parser.runner = runner
+
+ if (level === 0) {
+ parser.on('version', function (v) {
+ runner.emit('version', v)
+ })
+ }
+
+ parser.emittedSuite = false
+ parser.didAssert = false
+ parser.printed = false
+ parser.name = ''
+ parser.doingChild = null
+
+ parser.on('finish', function () {
+ if (!parser.parent)
+ runner.emit('end')
+ })
+
+ parser.on('child', function (child) {
+ //console.log('>>> 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
+ })
+
+ parser.on('comment', function (c) {
+ if (!this.printed && 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.
+ if (this.doingChild && this.doingChild.didAssert &&
+ this.doingChild.name === result.name) {
+ this.doingChild = null
+ return
+ }
+
+ this.didAssert = true
+ this.doingChild = null
+
+ emitTest(this, result)
+ })
+
+ parser.on('complete', function (results) {
+ this.results = results
+ if (this.suite)
+ runner.emit('suite end', this.suite)
+ })
+
+ // 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) {
+ //console.log('emitSuite', parser.emittedSuite, parser.level, parser.name)
+ 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)
+ parser.runner.emit('suite', suite)
+ }
+}
+
+function emitTest (parser, result) {
+ var runner = parser.runner
+ var test = new Test(result, parser)
+
+ if (parser.suite) {
+ //if (test.parent === parser)
+ // test.parent = parser.suite
+ parser.suite.tests.push(test)
+ }
+
+ 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) {
+ if (result.diag && result.diag.error)
+ return result.diag.error
+
+ var err = {
+ message: (result.name || '(unnamed error)').replace(/^Error: /, ''),
+ toString: function () {
+ return 'Error: ' + this.message
+ }
+ }
+
+ if (result.diag.stack) {
+ if (Array.isArray(result.diag.stack)) {
+ err.stack = err.toString() + '\n' +
+ result.diag.stack.map(function (s) {
+ return ' at ' + s
+ }).join('\n')
+ } else if (typeof result.diag.stack === 'string') {
+ err.stack = result.diag.stack
+ }
+ }
+
+ var hasFound = Object.prototype.hasOwnProperty.call(result, 'found')
+ var hasWanted = Object.prototype.hasOwnProperty.call(result, 'wanted')
+
+ if (hasFound)
+ err.actual = result.found
+
+ if (hasWanted)
+ err.expected = result.wanted
+
+ if (hasFound && hasWanted)
+ err.showDiff = true
+
+ return err
+}
+
diff --git a/lib/suite.js b/lib/suite.js
new file mode 100644
index 0000000..24b0cb3
--- /dev/null
+++ b/lib/suite.js
@@ -0,0 +1,21 @@
+// 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 = []
+}
+
+Suite.prototype.fullTitle = function () {
+ if (!this.parent)
+ return this.title || ''
+ else
+ return this.parent.fullTitle() + ' ' + (this.title || '').trim()
+}
diff --git a/lib/test.js b/lib/test.js
new file mode 100644
index 0000000..4511fdb
--- /dev/null
+++ b/lib/test.js
@@ -0,0 +1,28 @@
+// 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
+ 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 () {}'
+}
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..ac47027
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,698 @@
+/**
+ * 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(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/</g, '<')
+ .replace(/>/g, '>');
+};
+
+/**
+ * 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, '<')
+ .replace(/>/g, '>')
+ .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.hasOwnProperty(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');
+ }
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..83a2cbd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "tap-spec-reporter",
+ "version": "0.0.0",
+ "description": "Format TAP with indented sections to spec-like colored checks and exes.",
+ "main": "index.js",
+ "scripts": {
+ "test": "tap test/*.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/isaacs/tap-spec-reporter"
+ },
+ "author": "Isaac Z. Schlueter <i at izs.me> (http://blog.izs.me/)",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/isaacs/tap-spec-reporter/issues"
+ },
+ "homepage": "https://github.com/isaacs/tap-spec-reporter",
+ "dependencies": {
+ "supports-color": "^1.3.1",
+ "tap-parser": "^1.0.4"
+ },
+ "bin": "index.js"
+}
--
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