@japa/runner
Version:
A simple yet powerful testing framework for Node.js
259 lines (258 loc) • 8.47 kB
JavaScript
import { fileURLToPath } from "node:url";
import { ErrorsPrinter } from "@japa/errors-printer";
import { inspect } from "node:util";
import string from "@poppinss/string";
import timekeeper from "timekeeper";
import useColors from "@poppinss/colors";
import supportsColor from "supports-color";
import { parse } from "error-stack-parser-es";
import { Emitter, Group, Refiner, Runner, Suite, Test, TestContext } from "@japa/core";
import { AssertionError } from "node:assert";
var BaseReporter = class {
runner;
currentFileName;
currentSuiteName;
currentGroupName;
options;
constructor(options = {}) {
this.options = Object.assign({ stackLinesCount: 2 }, options);
}
printAggregates(summary) {
const tests = [];
if (summary.aggregates.passed) tests.push(colors.green(`${summary.aggregates.passed} passed`));
if (summary.aggregates.failed) tests.push(colors.red(`${summary.aggregates.failed} failed`));
if (summary.aggregates.todo) tests.push(colors.cyan(`${summary.aggregates.todo} todo`));
if (summary.aggregates.skipped) tests.push(colors.yellow(`${summary.aggregates.skipped} skipped`));
if (summary.aggregates.regression) tests.push(colors.magenta(`${summary.aggregates.regression} regression`));
this.runner.summaryBuilder.use(() => {
return [{
key: colors.dim("Tests"),
value: `${tests.join(", ")} ${colors.dim(`(${summary.aggregates.total})`)}`
}, {
key: colors.dim("Time"),
value: colors.dim(string.milliseconds.format(summary.duration))
}];
});
console.log(this.runner.summaryBuilder.build().join("\n"));
}
aggregateErrors(summary) {
const errorsList = [];
summary.failureTree.forEach((suite) => {
suite.errors.forEach((error) => errorsList.push({
title: suite.name,
...error
}));
suite.children.forEach((testOrGroup) => {
if (testOrGroup.type === "test") {
testOrGroup.errors.forEach((error) => {
errorsList.push({
title: `${suite.name} / ${testOrGroup.title}`,
...error
});
});
return;
}
testOrGroup.errors.forEach((error) => {
errorsList.push({
title: testOrGroup.name,
...error
});
});
testOrGroup.children.forEach((test) => {
test.errors.forEach((error) => {
errorsList.push({
title: `${testOrGroup.name} / ${test.title}`,
...error
});
});
});
});
});
return errorsList;
}
async printErrors(summary) {
if (!summary.failureTree.length) return;
const errorPrinter = new ErrorsPrinter({ framesMaxLimit: this.options.framesMaxLimit });
errorPrinter.printSectionHeader("ERRORS");
await errorPrinter.printErrors(this.aggregateErrors(summary));
}
onTestStart(_) {}
onTestEnd(_) {}
onGroupStart(_) {}
onGroupEnd(_) {}
onSuiteStart(_) {}
onSuiteEnd(_) {}
async start(_) {}
async end(_) {}
async printSummary(summary) {
await this.printErrors(summary);
console.log("");
if (summary.aggregates.total === 0 && !summary.hasError) {
console.log(colors.bgYellow().black(" NO TESTS EXECUTED "));
return;
}
if (summary.hasError) console.log(colors.bgRed().black(" FAILED "));
else console.log(colors.bgGreen().black(" PASSED "));
console.log("");
this.printAggregates(summary);
}
boot(runner, emitter) {
this.runner = runner;
emitter.on("test:start", (payload) => {
this.currentFileName = payload.meta.fileName;
this.onTestStart(payload);
});
emitter.on("test:end", (payload) => {
this.onTestEnd(payload);
});
emitter.on("group:start", (payload) => {
this.currentGroupName = payload.title;
this.currentFileName = payload.meta.fileName;
this.onGroupStart(payload);
});
emitter.on("group:end", (payload) => {
this.currentGroupName = void 0;
this.onGroupEnd(payload);
});
emitter.on("suite:start", (payload) => {
this.currentSuiteName = payload.name;
this.onSuiteStart(payload);
});
emitter.on("suite:end", (payload) => {
this.currentSuiteName = void 0;
this.onSuiteEnd(payload);
});
emitter.on("runner:start", async (payload) => {
await this.start(payload);
});
emitter.on("runner:end", async (payload) => {
await this.end(payload);
});
}
};
var TestContext$1 = class extends TestContext {
constructor(test) {
super();
this.test = test;
this.cleanup = (cleanupCallback) => {
test.cleanup(cleanupCallback);
};
}
};
var Test$1 = class extends Test {
static executedCallbacks = [];
static executingCallbacks = [];
throws(message, errorConstructor) {
const errorInPoint = new AssertionError({});
const existingExecutor = this.options.executor;
if (!existingExecutor) throw new Error("Cannot use \"test.throws\" method without a test callback");
this.options.executor = async (...args) => {
let raisedException;
try {
await existingExecutor(...args);
} catch (error) {
raisedException = error;
}
if (!raisedException) {
errorInPoint.message = "Expected test to throw an exception";
throw errorInPoint;
}
if (errorConstructor && !(raisedException instanceof errorConstructor)) {
errorInPoint.message = `Expected test to throw "${inspect(errorConstructor)}"`;
throw errorInPoint;
}
const exceptionMessage = raisedException.message;
if (!exceptionMessage || typeof exceptionMessage !== "string") {
errorInPoint.message = "Expected test to throw an exception with message property";
throw errorInPoint;
}
if (typeof message === "string") {
if (exceptionMessage !== message) {
errorInPoint.message = `Expected test to throw "${message}". Instead received "${raisedException.message}"`;
errorInPoint.actual = raisedException.message;
errorInPoint.expected = message;
throw errorInPoint;
}
return;
}
if (!message.test(exceptionMessage)) {
errorInPoint.message = `Expected test error to match "${message}" regular expression`;
throw errorInPoint;
}
};
return this;
}
};
var Group$1 = class extends Group {};
var Suite$1 = class extends Suite {};
var Runner$1 = class extends Runner {};
const colors = supportsColor.stdout ? useColors.ansi() : useColors.silent();
const icons = process.platform === "win32" && !process.env.WT_SESSION ? {
tick: "√",
cross: "×",
bullet: "*",
nodejs: "♦",
pointer: ">",
info: "i",
warning: "‼",
branch: " -",
squareSmallFilled: "[█]"
} : {
tick: "✔",
cross: "✖",
bullet: "●",
nodejs: "⬢",
pointer: "❯",
info: "ℹ",
warning: "⚠",
branch: "└──",
squareSmallFilled: "◼"
};
function formatPinnedTest(test) {
let fileName = "";
let line = 0;
let column = 0;
try {
test.options.meta.abort("Finding pinned test location");
} catch (error) {
const frame = parse(error).find((f) => f.fileName && f.lineNumber !== void 0 && f.columnNumber !== void 0 && !f.fileName.includes("node:") && !f.fileName.includes("ext:") && !f.fileName.includes("node_modules/"));
if (frame) {
fileName = frame.fileName.startsWith("file:") ? string.toUnixSlash(fileURLToPath(frame.fileName)) : string.toUnixSlash(frame.fileName);
line = frame.lineNumber;
column = frame.columnNumber;
}
}
return `${colors.yellow(` ⁃ ${test.title}`)}\n${colors.dim(` ${fileName}:${line}:${column}`)}`;
}
function printPinnedTests(runner) {
let pinnedTests = [];
runner.suites.forEach((suite) => {
suite.stack.forEach((testOrGroup) => {
if (testOrGroup instanceof Group$1) testOrGroup.tests.forEach(($test) => {
if ($test.isPinned) pinnedTests.push(formatPinnedTest($test));
});
else if (testOrGroup.isPinned) pinnedTests.push(formatPinnedTest(testOrGroup));
});
});
if (pinnedTests.length) {
console.log(colors.bgYellow().black(` ${pinnedTests.length} pinned test(s) found `));
pinnedTests.forEach((row) => console.log(row));
} else console.log(colors.bgYellow().black(` No pinned tests found `));
}
const dateTimeDoubles = {
reset() {
timekeeper.reset();
},
travelTo(durationOrDate) {
if (durationOrDate instanceof Date) timekeeper.travel(durationOrDate);
else {
const travelToDate = /* @__PURE__ */ new Date();
travelToDate.setMilliseconds(travelToDate.getMilliseconds() + string.milliseconds.parse(durationOrDate));
timekeeper.travel(travelToDate);
}
},
freeze(date) {
timekeeper.freeze(date);
}
};
export { Emitter as a, Runner$1 as c, TestContext$1 as d, BaseReporter as f, printPinnedTests as i, Suite$1 as l, dateTimeDoubles as n, Group$1 as o, icons as r, Refiner as s, colors as t, Test$1 as u };