Skip to content

Commit

Permalink
Merge pull request #8 from MailOnline/feat/jest-like
Browse files Browse the repository at this point in the history
Feat/jest like
  • Loading branch information
streamich authored Nov 9, 2017
2 parents 9e3bcac + d442459 commit 2dc4ffb
Show file tree
Hide file tree
Showing 22 changed files with 1,467 additions and 587 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# jest-tap-reporter
Jest retporter that outputs [TAP](https://testanything.org/tap-specification.html) results

Jest reporter that outputs valid [TAP](https://testanything.org/tap-specification.html) output and highlights similar to Jest's default reporter.

![jest-tap-reporter exaple](./docs/example.png)

## Installation

Expand All @@ -16,6 +19,7 @@ npm install --dev jest-tap-reporter
## Usage

#### Add to your jest configuration

```javascript
{
"reporters": [
Expand All @@ -25,6 +29,7 @@ npm install --dev jest-tap-reporter
```

#### Log levels

By default jest-tap-reporter will log the suite path and a resume at the end of the report. If you reduce the report to the bare minimum you can set the reporter logLevel to error.

```javascript
Expand All @@ -34,3 +39,5 @@ By default jest-tap-reporter will log the suite path and a resume at the end of
]
}
```

Available log levels are: `ERROR`, `WARN`, `INFO`.
3 changes: 3 additions & 0 deletions demo/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "mailonline/jest"
}
9 changes: 9 additions & 0 deletions demo/__snapshots__/foo.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Foobar snapshot that always changes 1`] = `
Object {
"time": 1510160275651,
}
`;

exports[`Foobar static snapshot 1`] = `"static text"`;
35 changes: 35 additions & 0 deletions demo/foo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
test('foo succeeds', () => {

});

test('bar fails', () => {
throw new Error('bar closed');
});

// Successful no-namer test.
test('', () => {});

// Failed no-namer test.
// eslint-disable-next-line jest/no-identical-title
test('', () => {
throw new TypeError('Unknown error occured.');
});

test('skipped test');

// eslint-disable-next-line jest/no-disabled-tests
xit('a sample todo test', () => {});

describe('Foobar', () => {
test('sample test that succeeds', () => {});

test('static snapshot', () => {
expect('static text').toMatchSnapshot();
});

test('snapshot that always changes', () => {
expect({
time: Date.now()
}).toMatchSnapshot();
});
});
1 change: 1 addition & 0 deletions demo/pass.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test('this is test suite that always passes', () => {});
Binary file added docs/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"test": "jest",
"demo": "jest --testPathPattern 'demo/.+\\.test\\.js' --testRegex 'demo/.+\\.test\\.js'",
"lint": "eslint --ignore-path .gitignore '**/*.js'",
"precommit": "npm run lint",
"prepush": "npm test",
Expand Down Expand Up @@ -38,10 +39,11 @@
],
"reporters": [
["./", {"logLevel": "INFO"}]
]
],
"testRegex": "test\\/.+\\.(test|spec)\\.jsx?$"
},
"dependencies": {
"chalk": "^2.3.0",
"ms": "^2.0.0"
"strip-ansi": "4.0.0"
}
}
241 changes: 241 additions & 0 deletions src/LineWriter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
const path = require('path');
const chalk = require('chalk');

const REG_TRACE_LINE = /\s*(.+)\((.+):([0-9]+):([0-9]+)\)$/;
const REG_INTERNALS = /^(node_modules|internal)\//;
const REG_AT = /^\s*at/;
const REG_ERROR = /^\s*Error:\s*/;

const MDASH = '\u2014';
const CIRCLE = '●';

const FAIL_TEXT = 'FAIL';
const PASS_TEXT = 'PASS';

const FAIL = chalk.supportsColor ?
chalk`{reset.inverse.bold.red ${FAIL_TEXT} }` :
` ${FAIL_TEXT} `;

const PASS = chalk.supportsColor ?
chalk`{reset.inverse.bold.green ${PASS_TEXT} }` :
` ${PASS_TEXT} `;

const formatComment = (line) => chalk`{hidden #} ${line}`;
const formatFailureMessageTraceLine = (description, relativeFilePath, row, column) =>
chalk`${description}({cyan ${relativeFilePath}}:{black.bold ${row}}:{black.bold ${column}})`;

class LineWriter {
constructor (logger, root) {
this.counter = 0;
this.logger = logger;
this.root = root;
this.planWritten = false;
}

getNextNumber () {
this.counter++;

return this.counter;
}

blank () {
this.logger.info('');
}

comment (line) {
this.logger.info(formatComment(line));
}

start (numSuites) {
this.blank();
this.blank();
this.comment(chalk`{green Starting...}`);

if (numSuites) {
this.commentLight(`${numSuites} test suites found.`);
}
}

commentLight (line) {
this.comment(chalk`{dim ${line}}`);
}

keyValue (key, value) {
// eslint-disable-next-line no-use-extend-native/no-use-extend-native
const keyFormatted = (key + ':').padEnd(12, ' ');

this.comment(chalk`{bold ${keyFormatted}} ${value}`);
}

keyValueList (key, list) {
let value = '';

for (const [label, style, num] of list) {
value += (value ? ', ' : '') + chalk`{${style} ${num} ${label}}`;
}

this.keyValue(key, value);
}

stats (name, failed, skipped, passed, total) {
const list = [];

if (total) {
if (failed) {
list.push(['failed', 'red.bold', failed]);
}

if (skipped) {
list.push(['skipped', 'yellow.bold', skipped]);
}

if (passed) {
list.push(['passed', 'green.bold', passed]);
}
}

list.push(['total', 'reset', total]);
this.keyValueList(name, list);
}

snapshots (failed, updated, added, passed, total) {
if (!total) {
return;
}

const list = [];

if (failed) {
list.push(['failed', 'red.bold', failed]);
}

if (updated) {
list.push(['updated', 'yellow.bold', updated]);
}

if (added) {
list.push(['added', 'green.bold', added]);
}

if (passed) {
list.push(['passed', 'green.bold', passed]);
}

list.push(['total', 'reset', total]);

this.keyValueList('Snapshots', list);
}

result (okNotOK, title) {
this.logger.log(okNotOK + chalk` {grey.dim ${this.getNextNumber()}} ${title}`);
}

passed (title) {
this.result(chalk`{green ok}`, title ? `${MDASH} ${title}` : '');
}

failed (title) {
this.result(chalk`{red not ok}`, chalk`{red.bold ${CIRCLE} ${title}}`);
}

pending (title) {
this.result(chalk`{yellow ok}`, chalk`{yellow #} {yellow.bold SKIP} ${title}`);
}

getPathRelativeToRoot (filePath) {
return path.relative(this.root, filePath);
}

formatFailureMessage (message) {
const [firstLine, ...lines] = message.split('\n');
const outputLines = [];
const whitespace = ' ';

const push = (line) => {
outputLines.push(line);
};
const pushTraceLine = (line) => push(chalk` {grey ${line}}`);
const pushTraceLineDim = (line) => pushTraceLine(chalk`{dim ${line}}`);

let firstLineFormatted = firstLine;

// Remove leading `Error: `
firstLineFormatted = firstLineFormatted.replace(REG_ERROR, '');

push('');
push(firstLineFormatted);
push('');

let internalsStarted = false;
let isFirstTraceLine = true;

for (const line of lines) {
if (line.match(REG_AT)) {
if (isFirstTraceLine) {
isFirstTraceLine = false;

const isLastLineBlank = outputLines[outputLines.length - 1] === '';

if (!isLastLineBlank) {
push('');
}
push(chalk`{bold.dim Stack trace:}`);
push('');
}

const matches = line.match(REG_TRACE_LINE);

if (matches) {
const [, description, file, row, column] = matches;
const relativeFilePath = path.relative(this.root, file);

if (relativeFilePath.match(REG_INTERNALS)) {
internalsStarted = true;
}

// eslint-disable-next-line no-lonely-if
if (internalsStarted) {
pushTraceLineDim(formatFailureMessageTraceLine(description, relativeFilePath, row, column));
} else {
pushTraceLine(formatFailureMessageTraceLine(description, relativeFilePath, row, column));
}
} else {
pushTraceLine(line);
}
} else {
push(line);
}
}

push('');

return outputLines.map((line) => formatComment(whitespace + line)).join('\n');
}

errors (messages) {
if (!messages.length) {
return;
}

const formattedMessages = messages.map((message) => this.formatFailureMessage(message)).join('\n');

this.logger.error(formattedMessages);
}

suite (isFail, dir, base) {
const label = isFail ? FAIL : PASS;

this.comment(chalk`${label} {grey ${this.getPathRelativeToRoot(dir)}${path.sep}}{bold ${base}}`);
}

plan (count = this.counter) {
if (this.planWritten) {
throw new Error('TAP test plan can be written only once.');
}

this.logger.log(chalk`{reset.inverse 1..${count}}`);
this.planWritten = true;
}
}

module.exports = LineWriter;
Loading

0 comments on commit 2dc4ffb

Please sign in to comment.