@plugjs/expect5
Version:
Unit Testing for the PlugJS Build System ========================================
288 lines (286 loc) • 13.6 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// test.ts
var test_exports = {};
__export(test_exports, {
Test: () => Test
});
module.exports = __toCommonJS(test_exports);
var import_node_assert = require("node:assert");
var import_plug = require("@plugjs/plug");
var import_asserts = require("@plugjs/plug/asserts");
var import_logging = require("@plugjs/plug/logging");
var import_paths = require("@plugjs/plug/paths");
var import_executable = require("./execution/executable.cjs");
var import_executor = require("./execution/executor.cjs");
var setup = __toESM(require("./execution/setup.cjs"), 1);
var import_diff = require("./expectation/diff.cjs");
var import_expect = require("./expectation/expect.cjs");
var import_print = require("./expectation/print.cjs");
var import_types = require("./expectation/types.cjs");
var _pending = "\u22EF";
var _success = "\u2714";
var _failure = "\u2718";
var _details = "\u2192";
var Test = class {
constructor(_options = {}) {
this._options = _options;
}
async pipe(files, context) {
(0, import_asserts.assert)(files.length, "No files available for running tests");
const {
globals = true,
genericErrorDiffs = true,
maxFailures = Number.POSITIVE_INFINITY,
summary = false
} = this._options;
if (globals) {
const anyGlobal = globalThis;
anyGlobal["describe"] = setup.describe;
anyGlobal["fdescribe"] = setup.fdescribe;
anyGlobal["xdescribe"] = setup.xdescribe;
anyGlobal["it"] = setup.it;
anyGlobal["fit"] = setup.fit;
anyGlobal["xit"] = setup.xit;
anyGlobal["afterAll"] = setup.afterAll;
anyGlobal["afterEach"] = setup.afterEach;
anyGlobal["beforeAll"] = setup.beforeAll;
anyGlobal["beforeEach"] = setup.beforeEach;
anyGlobal["xafterAll"] = setup.xafterAll;
anyGlobal["xafterEach"] = setup.xafterEach;
anyGlobal["xbeforeAll"] = setup.xbeforeAll;
anyGlobal["xbeforeEach"] = setup.xbeforeEach;
anyGlobal["skip"] = import_executable.skip;
anyGlobal["expect"] = import_expect.expect;
anyGlobal["log"] = import_logging.log;
anyGlobal["dirnameFromUrl"] = import_paths.dirnameFromUrl;
anyGlobal["filenameFromUrl"] = import_paths.filenameFromUrl;
}
const suite = new import_executable.Suite(void 0, "", async () => {
let count = 0;
for (const file of files.absolutePaths()) {
import_logging.log.debug("Importing", (0, import_logging.$p)(file), "in suite", (0, import_logging.$gry)(`(${++count}/${files.length})`));
await import(file);
}
});
await suite.setup();
const snum = suite.specs;
const fnum = files.length;
const smsg = (0, import_logging.$plur)(snum, "spec", "specs");
const fmsg = (0, import_logging.$plur)(fnum, "file", "files");
(0, import_asserts.assert)(snum, "No specs configured by test files");
const execution = (0, import_executor.runSuite)(suite);
execution.on("suite:start", (current) => {
if (current.parent === suite) {
if (suite.flag !== "only") context.log.notice("");
context.log.enter(import_logging.NOTICE, `${(0, import_logging.$wht)(current.name)}`);
context.log.notice("");
} else if (current.parent) {
context.log.enter(import_logging.NOTICE, `${(0, import_logging.$blu)(_details)} ${(0, import_logging.$wht)(current.name)}`);
} else {
context.log.notice(`Running ${smsg} from ${fmsg}`);
if (suite.flag === "only") context.log.notice("");
}
});
execution.on("suite:done", (current) => {
if (current.parent) context.log.leave();
});
execution.on("spec:start", (spec) => {
context.log.enter(import_logging.NOTICE, `${(0, import_logging.$blu)(_pending)} ${spec.name}`);
});
execution.on("spec:skip", (spec, ms) => {
if (suite.flag === "only") return context.log.leave();
context.log.leave(import_logging.WARN, `${(0, import_logging.$ylw)(_pending)} ${spec.name} ${(0, import_logging.$ms)(ms, (0, import_logging.$ylw)("skipped"))}`);
});
execution.on("spec:pass", (spec, ms, slow) => {
if (slow) {
context.log.leave(import_logging.WARN, `${(0, import_logging.$ylw)(_success)} ${spec.name} ${(0, import_logging.$ms)(ms, (0, import_logging.$ylw)("slow"))}`);
} else {
context.log.leave(import_logging.NOTICE, `${(0, import_logging.$grn)(_success)} ${spec.name} ${(0, import_logging.$ms)(ms)}`);
}
});
execution.on("spec:fail", (spec, ms, { number }) => {
context.log.leave(
import_logging.ERROR,
`${(0, import_logging.$red)(_failure)} ${spec.name} ${(0, import_logging.$ms)(ms)} ${(0, import_logging.$gry)("[")}${(0, import_logging.$red)("failed")}${(0, import_logging.$gry)("|")}${(0, import_logging.$red)(`${number}`)}${(0, import_logging.$gry)("]")}`
);
});
execution.on("hook:fail", (hook, ms, { number }) => {
context.log.error(`${(0, import_logging.$red)(_failure)} Hook "${hook.name}" ${(0, import_logging.$ms)(ms)} ${(0, import_logging.$gry)("[")}${(0, import_logging.$red)("failed")}${(0, import_logging.$gry)("|")}${(0, import_logging.$red)(`${number}`)}${(0, import_logging.$gry)("]")}`);
});
const { failed, passed, skipped, failures, time, records } = await execution.result;
const limit = Math.min(failures.length, maxFailures);
for (let i = 0; i < limit; i++) {
if (i === 0) context.log.error("");
const { source, error, number } = failures[i];
const names = [""];
for (let p = source.parent; p?.parent; p = p.parent) {
if (p) names.unshift(p.name);
}
const details = names.join(` ${(0, import_logging.$gry)(_details)} `) + (0, import_logging.$wht)(source.name);
context.log.enter(import_logging.ERROR, `${(0, import_logging.$gry)("[")}${(0, import_logging.$red)(number)}${(0, import_logging.$gry)("]:")} ${details}`);
dumpError(context.log, error, genericErrorDiffs);
context.log.leave();
}
if (summary) {
context.log.notice("");
context.log.notice((0, import_logging.$wht)("Test execution summary:"));
records.forEach((record) => dumpRecords(context.log, record));
}
const totals = [`${passed} ${(0, import_logging.$gry)("passed")}`];
if (skipped) totals.push(`${skipped} ${(0, import_logging.$gry)("skipped")}`);
if (failed) totals.push(`${failed} ${(0, import_logging.$gry)("failed")}`);
if (failures.length) totals.push(`${failures.length} ${(0, import_logging.$gry)("total failures")}`);
const epilogue = `${(0, import_logging.$gry)("(")}${totals.join((0, import_logging.$gry)(", "))}${(0, import_logging.$gry)(")")}`;
const message = `Ran ${smsg} from ${fmsg} ${epilogue} ${(0, import_logging.$ms)(time)}`;
if (failures.length) {
context.log.error(message);
throw new import_plug.BuildFailure();
} else if (suite.flag === "only") {
context.log.error("");
context.log.error(message);
throw new import_plug.BuildFailure('Suite running in focus ("only") mode');
} else if (skipped) {
context.log.warn("");
context.log.warn(message);
} else {
context.log.notice("");
context.log.notice(message);
}
}
};
function dumpRecords(log2, record) {
if (record.type === "suite") {
log2.enter(import_logging.NOTICE, `${(0, import_logging.$wht)(record.name)}`);
for (const r of record.records) dumpRecords(log2, r);
log2.leave();
} else if (record.type === "spec") {
switch (record.result) {
case "passed":
if (record.slow) {
log2.notice(`${(0, import_logging.$ylw)(_success)} ${record.name} ${(0, import_logging.$ms)(record.ms, (0, import_logging.$ylw)("slow"))}`);
} else {
log2.notice(`${(0, import_logging.$grn)(_success)} ${record.name} ${(0, import_logging.$ms)(record.ms)}`);
}
break;
case "skipped":
log2.notice(`${(0, import_logging.$ylw)(_pending)} ${record.name} ${(0, import_logging.$ms)(record.ms, (0, import_logging.$ylw)("skipped"))}`);
break;
case "failed":
log2.notice(
`${(0, import_logging.$red)(_failure)} ${record.name} ${(0, import_logging.$ms)(record.ms)} ${(0, import_logging.$gry)("[")}${(0, import_logging.$red)("failed")}${(0, import_logging.$gry)("|")}${(0, import_logging.$red)(`${record.failure}`)}${(0, import_logging.$gry)("]")}`
);
break;
}
} else if (record.type === "hook") {
log2.error(`${(0, import_logging.$red)(_failure)} Hook "${record.name}" ${(0, import_logging.$gry)("[")}${(0, import_logging.$red)("failed")}${(0, import_logging.$gry)("|")}${(0, import_logging.$red)(`${record.failure}`)}${(0, import_logging.$gry)("]")}`);
}
}
function dumpError(log2, error, genericErrorDiffs) {
if (error instanceof import_types.ExpectationError) {
log2.enter(import_logging.ERROR, `${(0, import_logging.$gry)("Expectation Error:")} ${(0, import_logging.$red)(error.message)}`);
(0, import_logging.githubAnnotation)({ type: "error", title: "Expectation Error" }, error.message);
try {
dumpProps(log2, 17, error);
dumpStack(log2, error);
if (error.diff) (0, import_print.printDiff)(log2, error.diff);
} finally {
log2.error("");
log2.leave();
}
} else if (error instanceof import_node_assert.AssertionError) {
const [message = "Unknown Error", ...lines] = error.message.split("\n");
log2.enter(import_logging.ERROR, `${(0, import_logging.$gry)("Assertion Error:")} ${(0, import_logging.$red)(message)}`);
(0, import_logging.githubAnnotation)({ type: "error", title: "Assertion Error" }, message);
try {
dumpProps(log2, 15, error);
dumpStack(log2, error);
if (genericErrorDiffs) {
if (!error.generatedMessage) for (const line of lines) log2.error(" ", line);
(0, import_print.printDiff)(log2, (0, import_diff.diff)(error.actual, error.expected));
} else {
while (lines.length && !lines[0]) lines.shift();
for (const line of lines) log2.error(" ", line);
}
} finally {
log2.error("");
log2.leave();
}
} else if (error instanceof Error) {
const message = error.message || (error instanceof import_plug.BuildFailure ? "Build Failure" : "Unknown Error");
const string = Object.getPrototypeOf(error)?.constructor?.name || "Error";
const type = string === "AssertionError" ? `${(0, import_logging.$gry)("Assertion Error")}: ` : string === "Error" ? "" : `${(0, import_logging.$gry)(string)}: `;
log2.enter(import_logging.ERROR, `${type}${(0, import_logging.$red)(message)}`);
(0, import_logging.githubAnnotation)({ type: "error", title: string }, message);
try {
dumpProps(log2, type.length, error);
dumpStack(log2, error);
if (genericErrorDiffs && ("actual" in error || "expected" in error)) {
(0, import_print.printDiff)(log2, (0, import_diff.diff)(error.actual, error.expected));
}
} finally {
log2.error("");
log2.leave();
}
} else {
log2.error((0, import_logging.$gry)("Uknown error:"), error);
}
}
function dumpProps(log2, pad, error) {
Object.keys(error).filter((k) => ![
"diff",
// expectations error,
"actual",
// assertion error, chai
"expected",
// assertion error, chai,
"generatedMessage",
// assertion error,
"message",
// error
"showDiff",
// chai
"stack"
// error
].includes(k)).filter((k) => !(error[k] === null)).filter((k) => !(error[k] === void 0)).forEach((k) => {
const value = error[k];
if (k === "code" && value === "ERR_ASSERTION") return;
const details = typeof value === "string" ? value : (0, import_types.stringifyValue)(value);
log2.error((0, import_logging.$gry)(`${k}:`.padStart(pad - 1)), (0, import_logging.$ylw)(details));
});
}
function dumpStack(log2, error) {
if (!error.stack) return log2.error("<no stack trace>");
error.stack.split("\n").filter((line) => line.match(/^\s+at\s+/)).map((line) => line.trim()).forEach((line) => log2.error(line));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Test
});
//# sourceMappingURL=test.cjs.map