@japa/errors-printer
Version:
Error printer to pretty print Japa errors
142 lines (141 loc) • 4.21 kB
JavaScript
// src/printer.ts
import { Youch } from "youch";
import colors from "@poppinss/colors";
import supportsColor from "supports-color";
import { diff as jestDiff } from "jest-diff";
var { columns } = process.stdout;
var ansi = supportsColor.stdout ? colors.ansi() : colors.silent();
var pointer = process.platform === "win32" && !process.env.WT_SESSION ? ">" : "\u276F";
var ErrorsPrinter = class {
#options;
constructor(options) {
this.#options = { stackLinesCount: 5, framesMaxLimit: 3, ...options };
}
/**
* Returns human readable message for error phase
*/
#getPhaseTitle(phase) {
switch (phase) {
case "setup":
return "Setup hook";
case "setup:cleanup":
return "Setup hook cleanup function";
case "teardown":
return "Teardown hook";
case "teardown:cleanup":
return "Teardown hook cleanup function";
}
}
/**
* Parses the error stack using Youch
*/
#parseErrorStack(error) {
return new Youch().toJSON(error, {
frameSourceBuffer: this.#options.stackLinesCount
});
}
/**
* Parsers chai assertion error
*/
async #parseAssertionError(error) {
const parsedError = await this.#parseErrorStack(error);
if (!("showDiff" in error) || error.showDiff) {
console.error();
const { actual, expected } = error;
const diff = jestDiff(expected, actual, {
expand: true,
includeChangeCounts: true
});
parsedError.message = `${parsedError.message}
${diff}`;
}
return parsedError;
}
/**
* Displays the error stack for a given error
*/
async #displayErrorStack(error) {
const ansiOutput = await new Youch().toANSI(error, {
frameSourceBuffer: this.#options.stackLinesCount
});
console.error(ansiOutput.trimEnd());
}
/**
* Display chai assertion error
*/
async #displayAssertionError(error) {
if (!("showDiff" in error) || error.showDiff) {
console.error();
const { actual, expected } = error;
const diff = jestDiff(expected, actual, {
expand: true,
includeChangeCounts: true
});
console.error(diff);
}
await this.#displayErrorStack(error);
}
/**
* Prints a section with heading and borders around it
*/
printSectionBorder(paging) {
const border = "\u2500".repeat(columns - (paging.length + 1));
console.error(ansi.red(`${border}${paging}\u2500`));
}
/**
* Prints section header with a centered title and
* borders around it
*/
printSectionHeader(title) {
const whitspacesWidth = (columns - title.length) / 2;
const [lhsWidth, rhsWidth] = Number.isInteger(whitspacesWidth) ? [whitspacesWidth, whitspacesWidth] : [whitspacesWidth - 1, whitspacesWidth + 1];
const borderLeft = ansi.red("\u2500".repeat(lhsWidth - 1));
const borderRight = ansi.red("\u2500".repeat(rhsWidth));
console.error(`${borderLeft}${ansi.bgRed().black(` ${title} `)}${borderRight}`);
}
/**
* Parses an error to JSON
*/
async parseError(error) {
if (error === null || Array.isArray(error) || typeof error !== "object") {
return {
message: String(error)
};
}
if ("actual" in error && "expected" in error) {
return this.#parseAssertionError(error);
}
return this.#parseErrorStack(error);
}
/**
* Pretty print the error to the console
*/
async printError(error) {
if (error === null || Array.isArray(error) || typeof error !== "object") {
console.error(`Error: ${error}`);
return;
}
if ("actual" in error && "expected" in error) {
await this.#displayAssertionError(error);
return;
}
await this.#displayErrorStack(error);
}
/**
* Print summary errors
*/
async printErrors(errors) {
const errorsCount = errors.length;
let index = 0;
for (let { phase, error, title } of errors) {
const label = phase === "test" ? title : `${title}: ${this.#getPhaseTitle(phase)}`;
console.error();
console.error(`${pointer} ${ansi.underline(label)}`);
await this.printError(error);
this.printSectionBorder(`[${++index}/${errorsCount}]`);
}
}
};
export {
ErrorsPrinter
};