japa
Version:
Lean test runner for Node.js
238 lines (237 loc) • 7.75 kB
JavaScript
;
/**
* @module Core
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/*
* japa
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
const ms_1 = __importDefault(require("ms"));
const chalk_1 = __importDefault(require("chalk"));
const jest_diff_1 = __importDefault(require("jest-diff"));
const utils_1 = require("../utils");
const Contracts_1 = require("../Contracts");
/**
* Icons to be used for different test and group
* statuses
*/
const icons = {
passed: chalk_1.default.green('✓'),
failed: chalk_1.default.red('✖'),
skipped: chalk_1.default.yellow('.'),
todo: chalk_1.default.cyan('!'),
regression: '',
};
/**
* Colors to be used for different test and group
* statuses
*/
const colors = {
passed: 'grey',
failed: 'red',
skipped: 'yellow',
todo: 'cyan',
regression: 'magenta',
};
/**
* List reporter to show the tests progress on stdout in
* a list format
*/
class ListReporter {
constructor(emitter) {
this._store = new utils_1.TestsStore();
emitter.on(Contracts_1.IEvents.STARTED, this._onStart.bind(this));
emitter.on(Contracts_1.IEvents.COMPLETED, this._onEnd.bind(this));
emitter.on(Contracts_1.IEvents.GROUPSTARTED, this._onGroupStart.bind(this));
emitter.on(Contracts_1.IEvents.GROUPCOMPLETED, this._onGroupEnd.bind(this));
emitter.on(Contracts_1.IEvents.TESTCOMPLETED, this._onTestEnd.bind(this));
}
get _indent() {
return this._store.activeGroup.title === 'root' ? '' : ' ';
}
/**
* When test runner has started. We just need to initiate
* the store on this event
*/
_onStart() {
console.log('');
this._store.open();
}
/**
* Everytime a new group starts
*/
_onGroupStart(group) {
this._store.recordGroup(group);
/**
* Log group title when it's not root
*/
if (group.title !== 'root') {
console.log(`\n${group.title}`);
}
}
/**
* Everytime a group has completed running all tests
*/
_onGroupEnd(group) {
this._store.endGroup(group);
}
/**
* Print count for a label
*/
_printCount(label, count) {
if (count) {
console.log(chalk_1.default.dim(`${label.padEnd(13)} : ${count}`));
}
}
/**
* Prints the error for the test. If error is an assertion error, then
* there is no need to print the stack.
*/
_printTestError(error) {
if ((0, utils_1.isCoreException)(error)) {
console.log(chalk_1.default.red(` ${error.message}`));
return;
}
const { actual, expected } = error;
if (actual && expected) {
const diff = (0, jest_diff_1.default)(expected, actual);
const mainLine = this._cleanupErrorStack(error.stack)[1];
if (mainLine) {
console.log(chalk_1.default.dim(` source => ${mainLine.trim().replace(/at\s+/, '')}`));
}
if (diff) {
console.log(diff);
}
else {
console.log(chalk_1.default.red(` Assertion Error: ${error.message}`));
}
return;
}
console.log(` ${this._formatErrorStack(error.stack)}`);
}
/**
* Everytime tests ends
*/
_onTestEnd(test) {
this._store.recordTest(test);
const icon = icons[test.status];
const message = chalk_1.default[colors[test.status]](test.title);
const duration = chalk_1.default.dim(`(${(0, ms_1.default)(test.duration)})`);
const regressionMessage = test.regressionMessage
? `\n${this._indent} ${chalk_1.default.magenta(test.regressionMessage)}`
: '';
console.log(`${this._indent}${icon} ${message} ${duration}${regressionMessage}`);
}
/**
* Returns a boolean if the error stack fine part of the
* japa core
*/
_isNativeStackLine(line) {
return ['Callable'].some((keyword) => line.includes(keyword));
}
/**
* Returns a boolean telling if error stack is part
* of japa core by finding the sorroundings.
*/
_isNativeSorroundedLine(line) {
return ['Generator.next', 'new Promise'].some((keyword) => line.includes(keyword));
}
/**
* Returns the title for the failing test
*/
_getFailingTitle(title) {
return chalk_1.default.red(`${icons.failed} ${title}`);
}
/**
* Cleans up the error stack
*/
_cleanupErrorStack(errorStack) {
let prevIsNative = false;
if (!errorStack) {
return [];
}
return errorStack
.split('\n')
.filter((line) => {
if (prevIsNative && this._isNativeSorroundedLine(line)) {
return false;
}
prevIsNative = this._isNativeStackLine(line);
return !prevIsNative;
});
}
/**
* Returns the error stack by filtering the japa core
* lines from it.
*/
_formatErrorStack(errorStack) {
if (!errorStack) {
return;
}
return this._cleanupErrorStack(errorStack).map((line, index) => {
if (index === 0) {
return chalk_1.default.red(line);
}
return chalk_1.default.dim(line);
}).join('\n');
}
/**
* When test runner stops
*/
_onEnd() {
this._store.close();
const report = this._store.getReport();
/**
* Show zero executed tests when no tests were ran
*/
if (report.total === 0 && report.groups.length === 0) {
console.log(chalk_1.default.bgMagenta.white(' ZERO TESTS EXECUTED '));
return;
}
const failedGroups = report.groups.filter((group) => {
return group.failedTests.length || group.failedHooks.length;
});
console.log('');
if (failedGroups.length) {
console.log(chalk_1.default.bgRed.white(' FAILED '));
}
else {
console.log(chalk_1.default.bgGreen.white(' PASSED '));
}
console.log('');
this._printCount('total', report.total);
this._printCount('failed', report.failedCount);
this._printCount('passed', report.passedCount);
this._printCount('todo', report.todoCount);
this._printCount('skipped', report.skippedCount);
this._printCount('regression', report.regressionCount);
this._printCount('duration', (0, ms_1.default)(report.duration));
failedGroups.forEach(({ title, failedHooks, failedTests }) => {
console.log('');
console.log(failedHooks.length ? this._getFailingTitle(title) : title);
if (failedHooks.length) {
const failedHook = failedHooks[0];
console.log(`${chalk_1.default.red(` (${failedHook.title})`)} ${this._formatErrorStack(failedHook.error.stack)}`);
}
if (failedTests.length) {
failedTests.forEach((test) => {
console.log('');
console.log(` ${this._getFailingTitle(test.title)}`);
this._printTestError(test.error);
});
}
});
}
}
function listReporter(emitter) {
return new ListReporter(emitter);
}
exports.default = listReporter;