'use strict'; /**

* @module XUnit
*/

/**

* Module dependencies.
*/

var Base = require('./base'); var utils = require('../utils'); var fs = require('fs'); var mkdirp = require('mkdirp'); var path = require('path'); var errors = require('../errors'); var createUnsupportedError = errors.createUnsupportedError; var constants = require('../runner').constants; var EVENT_TEST_PASS = constants.EVENT_TEST_PASS; var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL; var EVENT_RUN_END = constants.EVENT_RUN_END; var EVENT_TEST_PENDING = constants.EVENT_TEST_PENDING; var STATE_FAILED = require('../runnable').constants.STATE_FAILED; var inherits = utils.inherits; var escape = utils.escape;

/**

* Save timer references to avoid Sinon interfering (see GH-237).
*/

var Date = global.Date;

/**

* Expose `XUnit`.
*/

exports = module.exports = XUnit;

/**

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

function XUnit(runner, options) {

Base.call(this, runner, options);

var stats = this.stats;
var tests = [];
var self = this;

// the name of the test suite, as it will appear in the resulting XML file
var suiteName;

// the default name of the test suite if none is provided
var DEFAULT_SUITE_NAME = 'Mocha Tests';

if (options && options.reporterOptions) {
  if (options.reporterOptions.output) {
    if (!fs.createWriteStream) {
      throw createUnsupportedError('file output not supported in browser');
    }

    mkdirp.sync(path.dirname(options.reporterOptions.output));
    self.fileStream = fs.createWriteStream(options.reporterOptions.output);
  }

  // get the suite name from the reporter options (if provided)
  suiteName = options.reporterOptions.suiteName;
}

// fall back to the default suite name
suiteName = suiteName || DEFAULT_SUITE_NAME;

runner.on(EVENT_TEST_PENDING, function(test) {
  tests.push(test);
});

runner.on(EVENT_TEST_PASS, function(test) {
  tests.push(test);
});

runner.on(EVENT_TEST_FAIL, function(test) {
  tests.push(test);
});

runner.once(EVENT_RUN_END, function() {
  self.write(
    tag(
      'testsuite',
      {
        name: suiteName,
        tests: stats.tests,
        failures: 0,
        errors: stats.failures,
        skipped: stats.tests - stats.failures - stats.passes,
        timestamp: new Date().toUTCString(),
        time: stats.duration / 1000 || 0
      },
      false
    )
  );

  tests.forEach(function(t) {
    self.test(t);
  });

  self.write('</testsuite>');
});

}

/**

* Inherit from `Base.prototype`.
*/

inherits(XUnit, Base);

/**

* Override done to close the stream (if it's a file).
*
* @param failures
* @param {Function} fn
*/

XUnit.prototype.done = function(failures, fn) {

if (this.fileStream) {
  this.fileStream.end(function() {
    fn(failures);
  });
} else {
  fn(failures);
}

};

/**

* Write out the given line.
*
* @param {string} line
*/

XUnit.prototype.write = function(line) {

if (this.fileStream) {
  this.fileStream.write(line + '\n');
} else if (typeof process === 'object' && process.stdout) {
  process.stdout.write(line + '\n');
} else {
  Base.consoleLog(line);
}

};

/**

* Output tag for the given `test.`
*
* @param {Test} test
*/

XUnit.prototype.test = function(test) {

Base.useColors = false;

var attrs = {
  classname: test.parent.fullTitle(),
  name: test.title,
  time: test.duration / 1000 || 0
};

if (test.state === STATE_FAILED) {
  var err = test.err;
  var diff =
    Base.hideDiff || !err.actual || !err.expected
      ? ''
      : '\n' + Base.generateDiff(err.actual, err.expected);
  this.write(
    tag(
      'testcase',
      attrs,
      false,
      tag(
        'failure',
        {},
        false,
        escape(err.message) + escape(diff) + '\n' + escape(err.stack)
      )
    )
  );
} else if (test.isPending()) {
  this.write(tag('testcase', attrs, false, tag('skipped', {}, true)));
} else {
  this.write(tag('testcase', attrs, true));
}

};

/**

* HTML tag helper.
*
* @param name
* @param attrs
* @param close
* @param content
* @return {string}
*/

function tag(name, attrs, close, content) {

var end = close ? '/>' : '>';
var pairs = [];
var tag;

for (var key in attrs) {
  if (Object.prototype.hasOwnProperty.call(attrs, key)) {
    pairs.push(key + '="' + escape(attrs[key]) + '"');
  }
}

tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end;
if (content) {
  tag += content + '</' + name + end;
}
return tag;

}

XUnit.description = 'XUnit-compatible XML output';