@japa/runner
Version:
A simple yet powerful testing framework for Node.js
426 lines (425 loc) • 13.4 kB
JavaScript
import { f as BaseReporter, r as icons, t as colors } from "./helpers-C1bD1eod.js";
import { ErrorsPrinter } from "@japa/errors-printer";
import { stripVTControlCharacters } from "node:util";
import { relative } from "node:path";
import string from "@poppinss/string";
var DotReporter = class extends BaseReporter {
onTestEnd(payload) {
let output = "";
if (payload.isTodo) output = colors.cyan(icons.info);
else if (payload.hasError) output = colors.red(icons.cross);
else if (payload.isSkipped) output = colors.yellow(icons.bullet);
else if (payload.isFailing) output = colors.magenta(icons.squareSmallFilled);
else output = colors.green(icons.tick);
process.stdout.write(`${output}`);
}
async end() {
console.log("");
await this.printSummary(this.runner.getSummary());
}
};
var SpecReporter = class extends BaseReporter {
#isFirstLoneTest = true;
#getTestIcon(payload) {
if (payload.isTodo) return colors.cyan(icons.info);
if (payload.hasError) return colors.red(icons.cross);
if (payload.isSkipped) return colors.yellow(icons.bullet);
if (payload.isFailing) return colors.magenta(icons.squareSmallFilled);
return colors.green(icons.tick);
}
#getTestMessage(payload) {
const message = payload.title.expanded;
if (payload.isTodo) return colors.blue(message);
if (payload.hasError) return colors.red(message);
if (payload.isSkipped) return colors.yellow(message);
if (payload.isFailing) return colors.magenta(message);
return colors.grey(message);
}
#getSubText(payload) {
if (payload.isSkipped && payload.skipReason) return colors.dim(`${icons.branch} ${colors.italic(payload.skipReason)}`);
if (!payload.isFailing) return;
if (payload.hasError) {
const message = payload.errors.find((error) => error.phase === "test")?.error.message ?? `Test marked with ".fails()" must finish with an error`;
return colors.dim(`${icons.branch} ${colors.italic(message)}`);
}
if (payload.failReason) return colors.dim(`${icons.branch} ${colors.italic(payload.failReason)}`);
const testErrorMessage = payload.errors.find((error) => error.phase === "test");
if (testErrorMessage && testErrorMessage.error) return colors.dim(`${icons.branch} ${colors.italic(testErrorMessage.error.message)}`);
}
#getRelativeFilename(fileName) {
return relative(process.cwd(), fileName);
}
#printTest(payload) {
const icon = this.#getTestIcon(payload);
const message = this.#getTestMessage(payload);
const prefix = payload.isPinned ? colors.yellow("[PINNED] ") : "";
const indentation = this.currentFileName || this.currentGroupName ? " " : "";
const duration = colors.dim(`(${string.milliseconds.format(Number(payload.duration.toFixed(2)))})`);
const retries = payload.retryAttempt && payload.retryAttempt > 1 ? colors.dim(`(x${payload.retryAttempt}) `) : "";
let subText = this.#getSubText(payload);
subText = subText ? `\n${indentation} ${subText}` : "";
console.log(`${indentation}${icon} ${prefix}${retries}${message} ${duration}${subText}`);
}
#printGroup(payload) {
const title = this.currentSuiteName !== "default" ? `${this.currentSuiteName} / ${payload.title}` : payload.title;
const suffix = this.currentFileName ? colors.dim(` (${this.#getRelativeFilename(this.currentFileName)})`) : "";
console.log(`\n${title}${suffix}`);
}
onTestStart() {
if (this.currentFileName && this.#isFirstLoneTest) console.log(`\n${colors.dim(this.#getRelativeFilename(this.currentFileName))}`);
this.#isFirstLoneTest = false;
}
onTestEnd(payload) {
this.#printTest(payload);
}
onGroupStart(payload) {
this.#isFirstLoneTest = false;
this.#printGroup(payload);
}
onGroupEnd() {
this.#isFirstLoneTest = true;
}
async end() {
const summary = this.runner.getSummary();
await this.printSummary(summary);
}
};
const isNonErrorSymbol = Symbol("isNonError");
function defineProperty(object, key, value) {
Object.defineProperty(object, key, {
value,
writable: false,
enumerable: false,
configurable: false
});
}
function stringify(value) {
if (value === void 0) return "undefined";
if (value === null) return "null";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (typeof value === "bigint") return `${value}n`;
if (typeof value === "symbol") return value.toString();
if (typeof value === "function") return `[Function${value.name ? ` ${value.name}` : " (anonymous)"}]`;
if (value instanceof Error) try {
return String(value);
} catch {
return "<Unserializable error>";
}
try {
return JSON.stringify(value);
} catch {
try {
return String(value);
} catch {
return "<Unserializable value>";
}
}
}
var NonError = class NonError extends Error {
constructor(value, { superclass: Superclass = Error } = {}) {
if (NonError.isNonError(value)) return value;
if (value instanceof Error) throw new TypeError("Do not pass Error instances to NonError. Throw the error directly instead.");
super(`Non-error value: ${stringify(value)}`);
if (Superclass !== Error) Object.setPrototypeOf(this, Superclass.prototype);
defineProperty(this, "name", "NonError");
defineProperty(this, isNonErrorSymbol, true);
defineProperty(this, "isNonError", true);
defineProperty(this, "value", value);
}
static isNonError(value) {
return value?.[isNonErrorSymbol] === true;
}
static #handleCallback(callback, arguments_) {
try {
const result = callback(...arguments_);
if (result && typeof result.then === "function") return (async () => {
try {
return await result;
} catch (error) {
if (error instanceof Error) throw error;
throw new NonError(error);
}
})();
return result;
} catch (error) {
if (error instanceof Error) throw error;
throw new NonError(error);
}
}
static try(callback) {
return NonError.#handleCallback(callback, []);
}
static wrap(callback) {
return (...arguments_) => NonError.#handleCallback(callback, arguments_);
}
static [Symbol.hasInstance](instance) {
return NonError.isNonError(instance);
}
};
const list = [
Error,
EvalError,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError,
AggregateError,
globalThis.DOMException,
globalThis.AssertionError,
globalThis.SystemError
].filter(Boolean).map((constructor) => [constructor.name, constructor]);
const errorConstructors = new Map(list);
const errorFactories = /* @__PURE__ */ new Map();
const errorProperties = [
{
property: "name",
enumerable: false
},
{
property: "message",
enumerable: false
},
{
property: "stack",
enumerable: false
},
{
property: "code",
enumerable: true
},
{
property: "cause",
enumerable: false
},
{
property: "errors",
enumerable: false
}
];
const toJsonWasCalled = /* @__PURE__ */ new WeakSet();
const toJSON = (from) => {
toJsonWasCalled.add(from);
const json = from.toJSON();
toJsonWasCalled.delete(from);
return json;
};
const newError = (name) => {
const factory = errorFactories.get(name);
if (factory) return factory();
const ErrorConstructor = errorConstructors.get(name) ?? Error;
return ErrorConstructor === AggregateError ? new ErrorConstructor([]) : new ErrorConstructor();
};
const destroyCircular = ({ from, seen, to, forceEnumerable, maxDepth, depth, useToJSON, serialize }) => {
if (!to) if (Array.isArray(from)) to = [];
else if (!serialize && isErrorLike(from)) to = newError(from.name);
else to = {};
seen.push(from);
if (depth >= maxDepth) return to;
if (useToJSON && typeof from.toJSON === "function" && !toJsonWasCalled.has(from)) return toJSON(from);
const continueDestroyCircular = (value) => destroyCircular({
from: value,
seen: [...seen],
forceEnumerable,
maxDepth,
depth: depth + 1,
useToJSON,
serialize
});
for (const [key, value] of Object.entries(from)) {
if (value && value instanceof Uint8Array && value.constructor.name === "Buffer") {
to[key] = serialize ? "[object Buffer]" : value;
continue;
}
if (value !== null && typeof value === "object" && typeof value.pipe === "function") {
to[key] = serialize ? "[object Stream]" : value;
continue;
}
if (typeof value === "function") {
if (!serialize) to[key] = value;
continue;
}
if (serialize && typeof value === "bigint") {
to[key] = `${value}n`;
continue;
}
if (!value || typeof value !== "object") {
try {
to[key] = value;
} catch {}
continue;
}
if (!seen.includes(value)) {
to[key] = continueDestroyCircular(value);
continue;
}
to[key] = "[Circular]";
}
if (serialize || to instanceof Error) {
for (const { property, enumerable } of errorProperties) if (from[property] !== void 0 && from[property] !== null) Object.defineProperty(to, property, {
value: isErrorLike(from[property]) || Array.isArray(from[property]) ? continueDestroyCircular(from[property]) : from[property],
enumerable: forceEnumerable ? true : enumerable,
configurable: true,
writable: true
});
}
return to;
};
function serializeError(value, options = {}) {
const { maxDepth = Number.POSITIVE_INFINITY, useToJSON = true } = options;
if (typeof value === "object" && value !== null) return destroyCircular({
from: value,
seen: [],
forceEnumerable: true,
maxDepth,
depth: 0,
useToJSON,
serialize: true
});
if (typeof value === "function") value = "<Function>";
return destroyCircular({
from: new NonError(value),
seen: [],
forceEnumerable: true,
maxDepth,
depth: 0,
useToJSON,
serialize: true
});
}
function isErrorLike(value) {
return Boolean(value) && typeof value === "object" && typeof value.name === "string" && typeof value.message === "string" && typeof value.stack === "string";
}
var NdJSONReporter = class extends BaseReporter {
#getRelativeFilename(fileName) {
return relative(process.cwd(), fileName);
}
#serializeErrors(errors) {
return errors.map((error) => ({
phase: error.phase,
error: serializeError(error.error)
}));
}
onTestEnd(payload) {
console.log(JSON.stringify({
event: "test:end",
filePath: this.currentFileName,
relativePath: this.currentFileName ? this.#getRelativeFilename(this.currentFileName) : void 0,
title: payload.title,
duration: payload.duration,
failReason: payload.failReason,
isFailing: payload.isFailing,
skipReason: payload.skipReason,
isSkipped: payload.isSkipped,
isTodo: payload.isTodo,
isPinned: payload.isPinned,
retryAttempt: payload.retryAttempt,
retries: payload.retries,
errors: this.#serializeErrors(payload.errors)
}));
}
onGroupStart(payload) {
console.log(JSON.stringify({
event: "group:start",
title: payload.title
}));
}
onGroupEnd(payload) {
JSON.stringify({
event: "group:end",
title: payload.title,
errors: this.#serializeErrors(payload.errors)
});
}
onSuiteStart(payload) {
console.log(JSON.stringify({
event: "suite:start",
...payload
}));
}
onSuiteEnd(payload) {
console.log(JSON.stringify({
event: "suite:end",
name: payload.name,
hasError: payload.hasError,
errors: this.#serializeErrors(payload.errors)
}));
}
async end() {
const summary = this.runner.getSummary();
console.log(JSON.stringify({
aggregates: summary.aggregates,
duration: summary.duration,
failedTestsTitles: summary.failedTestsTitles,
hasError: summary.hasError
}));
}
};
var GithubReporter = class extends BaseReporter {
escapeMessage(value) {
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
}
escapeProperty(value) {
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A").replace(/:/g, "%3A").replace(/,/g, "%2C");
}
formatMessage({ command, properties, message }) {
let result = `::${command}`;
Object.entries(properties).forEach(([k, v], i) => {
result += i === 0 ? " " : ",";
result += `${k}=${this.escapeProperty(v)}`;
});
result += `::${this.escapeMessage(message)}`;
return result;
}
async getErrorAnnotation(printer, error) {
const parsedError = await printer.parseError(error.error);
if (!("frames" in parsedError)) return;
const mainFrame = parsedError.frames.find((frame) => frame.type === "app");
if (!mainFrame) return;
return this.formatMessage({
command: "error",
properties: {
file: string.toUnixSlash(relative(process.cwd(), mainFrame.fileName)),
title: error.title,
line: String(mainFrame.lineNumber),
column: String(mainFrame.columnNumber)
},
message: stripVTControlCharacters(parsedError.message)
});
}
async end() {
const summary = this.runner.getSummary();
const errorsList = this.aggregateErrors(summary);
const errorPrinter = new ErrorsPrinter(this.options);
for (let error of errorsList) {
const formatted = await this.getErrorAnnotation(errorPrinter, error);
if (formatted) console.log(`\n${formatted}`);
}
}
};
const spec = (options) => {
return {
name: "spec",
handler: (...args) => new SpecReporter(options).boot(...args)
};
};
const dot = (options) => {
return {
name: "dot",
handler: (...args) => new DotReporter(options).boot(...args)
};
};
const ndjson = (options) => {
return {
name: "ndjson",
handler: (...args) => new NdJSONReporter(options).boot(...args)
};
};
const github = (options) => {
return {
name: "github",
handler: (...args) => new GithubReporter(options).boot(...args)
};
};
export { spec as i, github as n, ndjson as r, dot as t };