'use strict'; var EventEmitter = require('events').EventEmitter; var path = require('path'); var util = require('util'); var chalk = require('chalk'); var isObj = require('is-obj'); var flatten = require('arr-flatten'); var figures = require('figures'); var formatter = require('./enhance-assert').formatter(); function RunStatus(opts) { if (!(this instanceof RunStatus)) { throw new TypeError('Class constructor RunStatus cannot be invoked without \'new\''); } EventEmitter.call(this); opts = opts || {}; this.prefixTitles = opts.prefixTitles !== false; this.hasExclusive = Boolean(opts.runOnlyExclusive); this.base = opts.base || ''; this.rejectionCount = 0; this.exceptionCount = 0; this.passCount = 0; this.knownFailureCount = 0; this.skipCount = 0; this.todoCount = 0; this.failCount = 0; this.fileCount = 0; this.testCount = 0; this.previousFailCount = 0; this.knownFailures = []; this.errors = []; this.stats = []; this.tests = []; Object.keys(RunStatus.prototype).forEach(function (key) { this[key] = this[key].bind(this); }, this); } util.inherits(RunStatus, EventEmitter); module.exports = RunStatus; RunStatus.prototype.observeFork = function (emitter) { emitter .on('teardown', this.handleTeardown) .on('stats', this.handleStats) .on('test', this.handleTest) .on('unhandledRejections', this.handleRejections) .on('uncaughtException', this.handleExceptions) .on('stdout', this.handleOutput.bind(this, 'stdout')) .on('stderr', this.handleOutput.bind(this, 'stderr')); }; function normalizeError(err) { if (!isObj(err)) { err = { message: err, stack: err }; } return err; } RunStatus.prototype.handleRejections = function (data) { this.rejectionCount += data.rejections.length; data.rejections.forEach(function (err) { err = normalizeError(err); err.type = 'rejection'; err.file = data.file; this.emit('error', err, this); this.errors.push(err); }, this); }; RunStatus.prototype.handleExceptions = function (data) { this.exceptionCount++; var err = normalizeError(data.exception); err.type = 'exception'; err.file = data.file; this.emit('error', err, this); this.errors.push(err); }; RunStatus.prototype.handleTeardown = function (data) { this.emit('dependencies', data.file, data.dependencies, this); }; RunStatus.prototype.handleStats = function (stats) { this.emit('stats', stats, this); if (this.hasExclusive && !stats.hasExclusive) { return; } if (!this.hasExclusive && stats.hasExclusive) { this.hasExclusive = true; this.testCount = 0; } this.testCount += stats.testCount; }; RunStatus.prototype.handleTest = function (test) { test.title = this.prefixTitle(test.file) + test.title; if (test.error) { if (test.error.powerAssertContext) { var message = formatter(test.error.powerAssertContext); if (test.error.originalMessage) { message = test.error.originalMessage + ' ' + message; } test.error.message = message; } if (test.error.name !== 'AssertionError') { test.error.message = 'failed with "' + test.error.message + '"'; } this.errors.push(test); } if (test.failing && !test.error) { this.knownFailures.push(test); } this.emit('test', test, this); }; RunStatus.prototype.prefixTitle = function (file) { if (!this.prefixTitles) { return ''; } var separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' '; var prefix = path.relative('.', file) .replace(this.base, function (match, offset) { // only replace this.base if it is found at the start of the path return offset === 0 ? '' : match; }) .replace(/\.spec/, '') .replace(/\.test/, '') .replace(/test\-/g, '') .replace(/\.js$/, '') .split(path.sep) .filter(function (p) { return p !== '__tests__'; }) .join(separator); if (prefix.length > 0) { prefix += separator; } return prefix; }; RunStatus.prototype.handleOutput = function (channel, data) { this.emit(channel, data, this); }; RunStatus.prototype.processResults = function (results) { // assemble stats from all tests this.stats = results.map(function (result) { return result.stats; }); this.tests = results.map(function (result) { return result.tests; }); this.tests = flatten(this.tests); this.passCount = sum(this.stats, 'passCount'); this.knownFailureCount = sum(this.stats, 'knownFailureCount'); this.skipCount = sum(this.stats, 'skipCount'); this.todoCount = sum(this.stats, 'todoCount'); this.failCount = sum(this.stats, 'failCount'); }; function sum(arr, key) { var result = 0; arr.forEach(function (item) { result += item[key]; }); return result; }