vitest-teamcity-reporter
Version:
A TeamCity reporter for vitest.
289 lines (288 loc) • 9.16 kB
JavaScript
//#region src/app/error/missing-result.error.ts
var MissingResultError = class extends Error {
constructor(testCase) {
super(`Test: "${testCase.fullName}" from - "${testCase.module.relativeModuleId}" missing a result after file test process finished`);
}
};
//#endregion
//#region src/app/escape.ts
const escapeSpecials = (str = "") => {
return str.toString().replace(/\x1B.*?m/g, "").replace(/\|/g, "||").replace(/\n/g, "|n").replace(/\r/g, "|r").replace(/\[/g, "|[").replace(/]/g, "|]").replace(/\u0085/g, "|x").replace(/\u2028/g, "|l").replace(/\u2029/g, "|p").replace(/'/g, "|'");
};
//#endregion
//#region src/app/messages/message.ts
var Message = class {
constructor(id, name) {
this.id = id;
this.name = name;
}
generateParameters(parameters) {
return Object.entries(parameters).map(([key, value]) => `${key}='${escapeSpecials(value)}'`).join(" ");
}
generateTeamcityMessage(type, flowId, parameters) {
return `##teamcity[${type} flowId='${flowId}' ${this.generateParameters(parameters)}]`;
}
};
//#endregion
//#region src/app/messages/run-error-message.ts
var RunErrorMessage = class extends Message {
constructor(flowId, name, identity) {
super(flowId, name);
this.identity = identity;
}
generate(type, parameters = {}) {
return this.generateTeamcityMessage(type, this.id, {
...parameters,
name: this.name
});
}
started() {
return this.generate("testStarted");
}
failed(error) {
const message = this.getErrorMessage(error);
return this.generate("testFailed", {
message,
details: error.stack ?? message
});
}
finished() {
return this.generate("testFinished", { duration: 0 });
}
buildProblem(error) {
return `##teamcity[buildProblem description='${escapeSpecials(`Vitest unhandled error: ${this.getErrorMessage(error)}`)}' identity='${escapeSpecials(this.identity)}']`;
}
getErrorMessage(error) {
return error.message || "Unknown unhandled error";
}
};
//#endregion
//#region src/app/messages/suite-message.ts
var SuiteMessage = class extends Message {
generate(type, parameters = {}) {
return this.generateTeamcityMessage(type, this.id, {
...parameters,
name: this.name
});
}
started() {
return this.generate("testSuiteStarted");
}
finished() {
return this.generate("testSuiteFinished");
}
};
//#endregion
//#region src/app/messages/test-message.ts
var TestMessage = class extends Message {
constructor(testCase) {
super(testCase.module.moduleId, testCase.name);
}
generate(type, parameters = {}) {
return this.generateTeamcityMessage(type, this.id, {
...parameters,
name: this.name
});
}
fail(error) {
return this.generate("testFailed", {
message: error.message,
details: error.stack ?? "",
actual: String(error.actual ?? ""),
expected: String(error.expected ?? "")
});
}
started() {
return this.generate("testStarted");
}
finished(duration) {
return this.generate("testFinished", { duration });
}
ignored() {
return this.generate("testIgnored");
}
stdOut(out) {
return this.generate("testStdOut", { out });
}
stdErr(out) {
return this.generate("testStdErr", { out });
}
log(type, out) {
return type === "stdout" ? this.stdOut(out) : this.stdErr(out);
}
};
//#endregion
//#region src/app/messages/test-metadata-message.ts
var TestMetadataMessage = class extends Message {
constructor(testCase) {
super(testCase.module.moduleId, testCase.name);
this.testCase = testCase;
}
generate(type, parameters = {}) {
return this.generateTeamcityMessage(type, this.id, parameters);
}
sourceFile() {
return this.generate("testMetadata", {
name: "sourceFile",
value: this.testCase.module.relativeModuleId
});
}
vitestFullName() {
return this.generate("testMetadata", {
name: "vitestFullName",
value: this.testCase.fullName
});
}
};
//#endregion
//#region src/app/printer.ts
var Printer = class {
constructor(logger) {
this.logger = logger;
this.testConsoleMap = /* @__PURE__ */ new Map();
this.reportedSuites = /* @__PURE__ */ new Set();
this.startedTests = /* @__PURE__ */ new Set();
}
onModuleCollected(testModule) {
const suiteMessage = new SuiteMessage(escapeSpecials(testModule.moduleId), escapeSpecials(testModule.relativeModuleId));
this.log(suiteMessage.started());
this.reportedSuites.add(testModule.moduleId);
}
onSuiteReady(testSuite) {
if (this.isSkippedOrTodo(testSuite)) return;
const suiteMessage = new SuiteMessage(escapeSpecials(testSuite.module.moduleId), escapeSpecials(testSuite.name));
this.log(suiteMessage.started());
this.reportedSuites.add(testSuite.id);
}
onTestReady(testCase) {
if (!this.isTestInReportedSuite(testCase)) return;
if (testCase.result().state === "skipped") {
const testMessage = new TestMessage(testCase);
this.log(testMessage.ignored());
return;
}
const testMessage = new TestMessage(testCase);
this.log(testMessage.started());
this.startedTests.add(testCase.id);
}
onTestResult(testCase) {
if (!this.isTestInReportedSuite(testCase)) return;
if (this.isSkippedOrTodo(testCase)) return;
const testMessage = new TestMessage(testCase);
if (!this.startedTests.has(testCase.id)) this.log(testMessage.started());
this.startedTests.delete(testCase.id);
const metadataMessage = new TestMetadataMessage(testCase);
this.log(metadataMessage.sourceFile());
this.log(metadataMessage.vitestFullName());
const result = testCase.result();
(this.testConsoleMap.get(testCase.id) ?? []).forEach((log) => {
this.log(testMessage.log(log.type, log.content));
});
this.testConsoleMap.delete(testCase.id);
const errors = this.getTestErrors(testCase);
const hasRealErrors = errors.length > 0 && !(errors[0] instanceof MissingResultError);
if (result.state === "failed" || hasRealErrors) errors.forEach((error) => {
this.log(testMessage.fail(error));
});
const diagnostic = testCase.diagnostic();
this.log(testMessage.finished(diagnostic?.duration ?? 0));
}
onSuiteResult(testSuite) {
if (this.isSkippedOrTodo(testSuite)) return;
const suiteMessage = new SuiteMessage(escapeSpecials(testSuite.module.moduleId), escapeSpecials(testSuite.name));
this.log(suiteMessage.finished());
this.reportedSuites.delete(testSuite.id);
}
onModuleEnd(testModule) {
const suiteMessage = new SuiteMessage(escapeSpecials(testModule.moduleId), escapeSpecials(testModule.moduleId));
this.log(suiteMessage.finished());
this.reportedSuites.delete(testModule.moduleId);
}
onTestRunEnd(unhandledErrors) {
if (unhandledErrors.length === 0) return;
const flowId = "vitest-unhandled-errors";
const suiteMessage = new SuiteMessage(flowId, "Vitest unhandled errors");
this.log(suiteMessage.started());
unhandledErrors.forEach((error, index) => {
const errorMessage = new RunErrorMessage(flowId, `Unhandled error ${index + 1}`, `vitest-unhandled-error-${index}`);
this.log(errorMessage.started());
this.log(errorMessage.failed(error));
this.log(errorMessage.finished());
this.log(errorMessage.buildProblem(error));
});
this.log(suiteMessage.finished());
}
addTestConsoleLog(id, log) {
const messages = this.testConsoleMap.get(id);
if (messages != null) messages.push(log);
else this.testConsoleMap.set(id, [log]);
}
log(message) {
this.logger.console.info(message);
}
isSkippedOrTodo(item) {
if (item.options.mode === void 0) return false;
return ["skip", "todo"].includes(item.options.mode);
}
isTestInReportedSuite(testCase) {
let current = testCase;
while (current.type !== "module") {
if (current.type === "suite" && !this.reportedSuites.has(current.id)) return false;
current = current.parent;
}
return true;
}
getTestErrors(testCase) {
const result = testCase.result();
if (result.errors !== void 0 && result.errors.length > 0) return [...result.errors];
let current = testCase.parent;
while (current.type !== "module") {
if (current.type === "suite") {
const suiteErrors = current.errors();
if (suiteErrors.length > 0) return suiteErrors;
}
current = current.parent;
}
const moduleErrors = current.errors();
if (moduleErrors.length > 0) return moduleErrors;
return [new MissingResultError(testCase)];
}
};
//#endregion
//#region src/app/reporter.ts
var TeamCityReporter = class {
onInit(ctx) {
this.logger = ctx.logger;
this.printer = new Printer(this.logger);
}
onTestModuleCollected(testModule) {
this.printer.onModuleCollected(testModule);
}
onTestSuiteReady(testSuite) {
this.printer.onSuiteReady(testSuite);
}
onTestCaseReady(testCase) {
this.printer.onTestReady(testCase);
}
onTestCaseResult(testCase) {
this.printer.onTestResult(testCase);
}
onTestSuiteResult(testSuite) {
this.printer.onSuiteResult(testSuite);
}
onTestModuleEnd(testModule) {
this.printer.onModuleEnd(testModule);
}
onTestRunEnd(_testModules, unhandledErrors) {
this.printer.onTestRunEnd(unhandledErrors);
}
onUserConsoleLog(log) {
if (log.taskId != null) this.printer.addTestConsoleLog(log.taskId, log);
else this.logger.console.log(log);
}
};
//#endregion
//#region src/app/index.ts
var app_default = TeamCityReporter;
//#endregion
module.exports = app_default;