"use strict";
/**
* @typedef {import('../runner.js')} Runner
* @typedef {import('../test.js')} Test
*/
/**
* @module XUnit
*/
/**
* Module dependencies.
*/
var Base = require("./base");
var utils = require("../utils");
var fs = require("node:fs");
var path = require("node: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");
}
fs.mkdirSync(path.dirname(options.reporterOptions.output), {
recursive: true,
});
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, options);
});
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, options) {
Base.useColors = false;
var attrs = {
classname: test.parent.fullTitle(),
name: test.title,
file: testFilePath(test.file, options),
time: test.duration / 1000 || 0,
};
if (test.state === STATE_FAILED) {
var err = test.err;
var diff =
!Base.hideDiff && Base.showDiff(err)
? "\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;
}
function testFilePath(filepath, options) {
if (
options &&
options.reporterOptions &&
options.reporterOptions.showRelativePaths
) {
return path.relative(process.cwd(), filepath);
}
return filepath;
}
XUnit.description = "XUnit-compatible XML output";