UNPKG

japa

Version:

Lean test runner for Node.js

238 lines (237 loc) 7.75 kB
"use strict"; /** * @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;