Skip to content

Commit

Permalink
Merge branch '3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
scottcorgan committed Apr 2, 2015
2 parents e109209 + f6cdb47 commit 048081a
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 135 deletions.
9 changes: 7 additions & 2 deletions bin/cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ process.stdin
.pipe(tapSpec)
.pipe(process.stdout);

process.on('exit', function () {
if (tapSpec.errors.length || !tapSpec.results.ok) {
process.on('exit', function (status) {

if (status === 1) {
process.exit(1);
}

if (tapSpec.failed) {
process.exit(1);
}
});
246 changes: 117 additions & 129 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,167 +1,155 @@
var fs = require('fs');

var tapOut = require('tap-out');
var through = require('through2');
var parser = require('tap-parser');
var duplexer = require('duplexer');
var format = require('chalk');
var prettyMs = require('pretty-ms');
var _ = require('lodash');
var repeat = require('repeat-string');

var assertCount = 0;
var symbols = {
ok: '\u2713',
err: '\u2717'
};

// win32 console default output fonts don't support tick/cross
if (process && process.platform === 'win32') {
symbols = {
ok: '\u221A',
err: '\u00D7'
};
}
var symbols = require('./lib/utils/symbols');
var lTrimList = require('./lib/utils/l-trim-list');

module.exports = function() {
module.exports = function (spec) {

spec = spec || {};

// TODO: document
var OUTPUT_PADDING = spec.padding || ' ';

var output = through();
var parser = tapOut();
var stream = duplexer(parser, output);
var startTime = new Date().getTime();

var out = through();
var tap = parser();
var dup = duplexer(tap, out);
var previousTestName = '';
var currentTestName = '';
var testNumber = 0;
var errors = [];
var res;

out.push('\n');
output.push('\n');

// Comments from tests
tap.on('comment', function (comment) {
parser.on('test', function (test) {

previousTestName = currentTestName;
currentTestName = comment;
output.push('\n' + pad(test.name) + '\n\n');
});

// Passing assertions
parser.on('pass', function (assertion) {

// Keep track of test number
if (currentTestName !== previousTestName) {
testNumber += 1;
}

if (/^tests\s+[1-9]/gi.test(comment)) {
return;
}
var glyph = format.green(symbols.ok);
var name = format.dim(assertion.name);

else if (/^pass\s+[1-9]/gi.test(comment)) {
return;
}
output.push(pad(' ' + glyph + ' ' + name + '\n'));
});

// Failing assertions
parser.on('fail', function (assertion) {

else if (/^fail\s+[1-9]/gi.test(comment)) {
return;
}
var glyph = format.red(symbols.err);
var name = format.red.bold(assertion.name);

else if (/^ok$/gi.test(comment)) {
return;
}
output.push(pad(' ' + glyph + ' ' + name + '\n'));

else {
// Test name
comment = comment + '\n';
out.push('\n');
}

out.push(' ' + comment + '\n');
stream.failed = true;
});

// Asserts
tap.on('assert', function (res) {

var output = (res.ok)
? format.green(symbols.ok)
: format.red(symbols.err);

// All done
parser.on('output', function (results) {

assertCount += 1;
output.push('\n\n');

if (!res.ok) {
errors.push({
assertName: res.name,
testName: currentTestName
});
if (results.fail.length > 0) {
output.push(formatErrors(results));
output.push('\n');
}

out.push(' ' + output + ' ' + format.gray(res.name) + '\n');
});

// Generic outputs
tap.on('extra', function (extra) {

out.push(' ' + format.yellow(extra) + '\n');
output.push(formatTotals(results));
output.push('\n\n\n');
});

// All done
tap.on('results', function (_res) {
// Utils

function formatErrors (results) {

res = _res
var failCount = results.fail.length;
var past = (failCount === 1) ? 'was' : 'were';
var plural = (failCount === 1) ? 'failure' : 'failures';

var out = '\n' + pad(format.red.bold('Failed Tests:') + ' There ' + past + ' ' + format.red.bold(failCount) + ' ' + plural + '\n');
out += formatFailedAssertions(results);

if (errors.length) {
var past = (errors.length == 1) ? 'was' : 'were';
var plural = (errors.length == 1) ? 'failure' : 'failures';
return out;
}

out.push(' ' + format.red.bold('Failed Tests: '));
out.push('There ' + past + ' ' + format.red.bold(errors.length) + ' ' + plural + '\n\n');
function formatTotals (results) {

return _.filter([
pad('total: ' + results.asserts.length),
pad(format.green('passing: ' + results.pass.length)),
results.fail.length > 0 ? pad(format.red('failing: ' + results.fail.length)) : null,
pad('duration: ' + prettyMs(new Date().getTime() - startTime)) // TODO: actually calculate this
], _.identity).join('\n');
}

function formatFailedAssertions (results) {

var out = '';

var groupedAssertions = _.groupBy(results.fail, function (assertion) {
return assertion.test;
});

_.each(groupedAssertions, function (assertions, testNumber) {

// Group the errors by test name
var groupedErrors = {};
errors.forEach(function (error) {

var name = error.testName;
groupedErrors[name] = groupedErrors[name] || [];
groupedErrors[name].push(error);
});
// Wrie failed assertion's test name
var test = _.find(results.tests, {number: parseInt(testNumber)});
out += '\n' + pad(' ' + test.name + '\n\n');

Object.keys(groupedErrors).forEach(function (name) {

var errors = groupedErrors[name];
// Write failed assertion
_.each(assertions, function (assertion) {

out.push(' ' + name + '\n\n');

errors.forEach(function (error) {

out.push(' ' + format.red(symbols.err) + ' ' + format.red(error.assertName) + '\n');
});

out.push('\n');
out += pad(' ' + format.red(symbols.err) + ' ' + format.red(assertion.name)) + '\n';
out += formatFailedAssertionDetail(assertion) + '\n';
});

out.push('\n');
}
});

// Test number
out.push(' total: ' + res.asserts.length + '\n');
// Pass number
out.push(format.green(' passing: ' + res.pass.length) + '\n');
// Fail number
if (res.fail.length > 0) {
out.push(format.red(' failing: ' + res.fail.length) + '\n');
}
// Duration
out.push(' duration: ' + prettyMs(new Date().getTime() - startTime) + '\n');
return out;
}

function formatFailedAssertionDetail (assertion) {

out.push('\n');
var out = '';

if (res.ok) {
out.push(' ' + format.green.bold('All tests pass!') + '\n');
}
var filepath = assertion.error.at.file;
var contents = fs.readFileSync(filepath).toString().split('\n');
var line = contents[assertion.error.at.line - 1];
var previousLine = contents[assertion.error.at.line - 2];
var nextLine = contents[assertion.error.at.line];
var lineNumber = parseInt(assertion.error.at.line);
var previousLineNumber = parseInt(assertion.error.at.line) - 1;
var nextLineNumber = parseInt(assertion.error.at.line) + 1;

// Catching no assertions and formatting failing tests
if (!res.ok && assertCount === 0) {
out.push(' ' + format.red('Failed:') + ' No assertions found.\n\n');
}
else if (!res.ok && res.fail.length === 0) {
out.push('\n')
}
var lines = lTrimList([
line,
previousLine,
nextLine
]);

// Expose errors and res on returned dup stream
dup.errors = errors;
dup.results = res;
});
var atCharacterPadding = parseInt(assertion.error.at.character) + parseInt(lineNumber.toString().length) + 2;

out += pad(' ' + format.dim(filepath)) + '\n';

return dup;
}
out += pad(' ' + repeat(' ', atCharacterPadding) + format.red('v') + "\n");
out += pad(' ' + format.dim(previousLineNumber + '. ' + lines[1])) + '\n';
out += pad(' ' + lineNumber + '. ' + lines[0]) + '\n';
out += pad(' ' + format.dim(nextLineNumber + '. ' + lines[2])) + '\n';
out += pad(' ' + repeat(' ', atCharacterPadding) + format.red('^') + "\n");

return out;
}

function pad (str) {

return OUTPUT_PADDING + str;
}

return stream;
};
22 changes: 22 additions & 0 deletions lib/utils/l-trim-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
var _ = require('lodash');

module.exports = function (lines) {

var leftPadding;

// Get minimum padding count
_.each(lines, function (line) {

var spaceLen = line.match(/^\s+/)[0].length;

if (leftPadding === undefined || spaceLen < leftPadding) {
leftPadding = spaceLen;
}
});

// Strip padding at beginning of line
return _.map(lines, function (line) {

return line.slice(leftPadding);
});
}
14 changes: 14 additions & 0 deletions lib/utils/symbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var symbols = {
ok: '\u2713',
err: '\u2717'
};

// win32 console default output fonts don't support tick/cross
if (process && process.platform === 'win32') {
symbols = {
ok: '\u221A',
err: '\u00D7'
};
}

module.exports = symbols;
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tap-spec",
"version": "2.2.2",
"version": "3.0.0",
"description": "Formatted TAP output like Mocha's spec reporter",
"main": "index.js",
"scripts": {
Expand All @@ -27,16 +27,17 @@
"dependencies": {
"chalk": "^1.0.0",
"duplexer": "^0.1.1",
"lodash": "^3.6.0",
"pretty-ms": "^1.0.0",
"tap-parser": "^0.7.0",
"repeat-string": "^1.5.2",
"tap-out": "^1.1.3",
"through2": "^0.6.3"
},
"bin": {
"tspec": "bin/cmd.js",
"tap-spec": "bin/cmd.js"
},
"devDependencies": {
"lodash": "^3.1.0",
"tapes": "^0.4.1"
"tapes": "^2.0.0"
}
}

0 comments on commit 048081a

Please sign in to comment.