'use strict'; /**

* @module TAP
*/

/**

* Module dependencies.
*/

var util = require('util'); var Base = require('./base'); var constants = require('../runner').constants; var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; var EVENT_RUN_BEGIN = constants.EVENT_RUN_BEGIN; var EVENT_RUN_END = constants.EVENT_RUN_END; var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; var EVENT_TEST_END = constants.EVENT_TEST_END; var inherits = require('../utils').inherits; var sprintf = util.format;

/**

* Expose `TAP`.
*/

exports = module.exports = TAP;

/**

* Constructs a new `TAP` reporter instance.
*
* @public
* @class
* @memberof Mocha.reporters
* @extends Mocha.reporters.Base
* @param {Runner} runner - Instance triggers reporter actions.
* @param {Object} [options] - runner options
*/

function TAP(runner, options) {

Base.call(this, runner, options);

var self = this;
var n = 1;

var tapVersion = '12';
if (options && options.reporterOptions) {
  if (options.reporterOptions.tapVersion) {
    tapVersion = options.reporterOptions.tapVersion.toString();
  }
}

this._producer = createProducer(tapVersion);

runner.once(EVENT_RUN_BEGIN, function() {
  var ntests = runner.grepTotal(runner.suite);
  self._producer.writeVersion();
  self._producer.writePlan(ntests);
});

runner.on(EVENT_TEST_END, function() {
  ++n;
});

runner.on(EVENT_TEST_PENDING, function(test) {
  self._producer.writePending(n, test);
});

runner.on(EVENT_TEST_PASS, function(test) {
  self._producer.writePass(n, test);
});

runner.on(EVENT_TEST_FAIL, function(test, err) {
  self._producer.writeFail(n, test, err);
});

runner.once(EVENT_RUN_END, function() {
  self._producer.writeEpilogue(runner.stats);
});

}

/**

* Inherit from `Base.prototype`.
*/

inherits(TAP, Base);

/**

* Returns a TAP-safe title of `test`.
*
* @private
* @param {Test} test - Test instance.
* @return {String} title with any hash character removed
*/

function title(test) {

return test.fullTitle().replace(/#/g, '');

}

/**

* Writes newline-terminated formatted string to reporter output stream.
*
* @private
* @param {string} format - `printf`-like format string
* @param {...*} [varArgs] - Format string arguments
*/

function println(format, varArgs) {

var vargs = Array.from(arguments);
vargs[0] += '\n';
process.stdout.write(sprintf.apply(null, vargs));

}

/**

* Returns a `tapVersion`-appropriate TAP producer instance, if possible.
*
* @private
* @param {string} tapVersion - Version of TAP specification to produce.
* @returns {TAPProducer} specification-appropriate instance
* @throws {Error} if specification version has no associated producer.
*/

function createProducer(tapVersion) {

var producers = {
  '12': new TAP12Producer(),
  '13': new TAP13Producer()
};
var producer = producers[tapVersion];

if (!producer) {
  throw new Error(
    'invalid or unsupported TAP version: ' + JSON.stringify(tapVersion)
  );
}

return producer;

}

/**

* @summary
* Constructs a new TAPProducer.
*
* @description
* <em>Only</em> to be used as an abstract base class.
*
* @private
* @constructor
*/

function TAPProducer() {}

/**

* Writes the TAP version to reporter output stream.
*
* @abstract
*/

TAPProducer.prototype.writeVersion = function() {};

/**

* Writes the plan to reporter output stream.
*
* @abstract
* @param {number} ntests - Number of tests that are planned to run.
*/

TAPProducer.prototype.writePlan = function(ntests) {

println('%d..%d', 1, ntests);

};

/**

* Writes that test passed to reporter output stream.
*
* @abstract
* @param {number} n - Index of test that passed.
* @param {Test} test - Instance containing test information.
*/

TAPProducer.prototype.writePass = function(n, test) {

println('ok %d %s', n, title(test));

};

/**

* Writes that test was skipped to reporter output stream.
*
* @abstract
* @param {number} n - Index of test that was skipped.
* @param {Test} test - Instance containing test information.
*/

TAPProducer.prototype.writePending = function(n, test) {

println('ok %d %s # SKIP -', n, title(test));

};

/**

* Writes that test failed to reporter output stream.
*
* @abstract
* @param {number} n - Index of test that failed.
* @param {Test} test - Instance containing test information.
* @param {Error} err - Reason the test failed.
*/

TAPProducer.prototype.writeFail = function(n, test, err) {

println('not ok %d %s', n, title(test));

};

/**

* Writes the summary epilogue to reporter output stream.
*
* @abstract
* @param {Object} stats - Object containing run statistics.
*/

TAPProducer.prototype.writeEpilogue = function(stats) {

// :TBD: Why is this not counting pending tests?
println('# tests ' + (stats.passes + stats.failures));
println('# pass ' + stats.passes);
// :TBD: Why are we not showing pending results?
println('# fail ' + stats.failures);

};

/**

* @summary
* Constructs a new TAP12Producer.
*
* @description
* Produces output conforming to the TAP12 specification.
*
* @private
* @constructor
* @extends TAPProducer
* @see {@link https://testanything.org/tap-specification.html|Specification}
*/

function TAP12Producer() {

/**
 * Writes that test failed to reporter output stream, with error formatting.
 * @override
 */
this.writeFail = function(n, test, err) {
  TAPProducer.prototype.writeFail.call(this, n, test, err);
  if (err.message) {
    println(err.message.replace(/^/gm, '  '));
  }
  if (err.stack) {
    println(err.stack.replace(/^/gm, '  '));
  }
};

}

/**

* Inherit from `TAPProducer.prototype`.
*/

inherits(TAP12Producer, TAPProducer);

/**

* @summary
* Constructs a new TAP13Producer.
*
* @description
* Produces output conforming to the TAP13 specification.
*
* @private
* @constructor
* @extends TAPProducer
* @see {@link https://testanything.org/tap-version-13-specification.html|Specification}
*/

function TAP13Producer() {

/**
 * Writes the TAP version to reporter output stream.
 * @override
 */
this.writeVersion = function() {
  println('TAP version 13');
};

/**
 * Writes that test failed to reporter output stream, with error formatting.
 * @override
 */
this.writeFail = function(n, test, err) {
  TAPProducer.prototype.writeFail.call(this, n, test, err);
  var emitYamlBlock = err.message != null || err.stack != null;
  if (emitYamlBlock) {
    println(indent(1) + '---');
    if (err.message) {
      println(indent(2) + 'message: |-');
      println(err.message.replace(/^/gm, indent(3)));
    }
    if (err.stack) {
      println(indent(2) + 'stack: |-');
      println(err.stack.replace(/^/gm, indent(3)));
    }
    println(indent(1) + '...');
  }
};

function indent(level) {
  return Array(level + 1).join('  ');
}

}

/**

* Inherit from `TAPProducer.prototype`.
*/

inherits(TAP13Producer, TAPProducer);

TAP.description = 'TAP-compatible output';