UNPKG

@plugjs/expect5

Version:

Unit Testing for the PlugJS Build System ========================================

253 lines (252 loc) 9.99 kB
// test.ts import { AssertionError } from "node:assert"; import { BuildFailure } from "@plugjs/plug"; import { assert } from "@plugjs/plug/asserts"; import { $blu, $grn, $gry, $ms, $p, $plur, $red, $wht, $ylw, ERROR, NOTICE, WARN, githubAnnotation, log } from "@plugjs/plug/logging"; import { dirnameFromUrl, filenameFromUrl } from "@plugjs/plug/paths"; import { Suite, skip } from "./execution/executable.mjs"; import { runSuite } from "./execution/executor.mjs"; import * as setup from "./execution/setup.mjs"; import { diff } from "./expectation/diff.mjs"; import { expect } from "./expectation/expect.mjs"; import { printDiff } from "./expectation/print.mjs"; import { ExpectationError, stringifyValue } from "./expectation/types.mjs"; var _pending = "\u22EF"; var _success = "\u2714"; var _failure = "\u2718"; var _details = "\u2192"; var Test = class { constructor(_options = {}) { this._options = _options; } async pipe(files, context) { 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"] = skip; anyGlobal["expect"] = expect; anyGlobal["log"] = log; anyGlobal["dirnameFromUrl"] = dirnameFromUrl; anyGlobal["filenameFromUrl"] = filenameFromUrl; } const suite = new Suite(void 0, "", async () => { let count = 0; for (const file of files.absolutePaths()) { log.debug("Importing", $p(file), "in suite", $gry(`(${++count}/${files.length})`)); await import(file); } }); await suite.setup(); const snum = suite.specs; const fnum = files.length; const smsg = $plur(snum, "spec", "specs"); const fmsg = $plur(fnum, "file", "files"); assert(snum, "No specs configured by test files"); const execution = runSuite(suite); execution.on("suite:start", (current) => { if (current.parent === suite) { if (suite.flag !== "only") context.log.notice(""); context.log.enter(NOTICE, `${$wht(current.name)}`); context.log.notice(""); } else if (current.parent) { context.log.enter(NOTICE, `${$blu(_details)} ${$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(NOTICE, `${$blu(_pending)} ${spec.name}`); }); execution.on("spec:skip", (spec, ms) => { if (suite.flag === "only") return context.log.leave(); context.log.leave(WARN, `${$ylw(_pending)} ${spec.name} ${$ms(ms, $ylw("skipped"))}`); }); execution.on("spec:pass", (spec, ms, slow) => { if (slow) { context.log.leave(WARN, `${$ylw(_success)} ${spec.name} ${$ms(ms, $ylw("slow"))}`); } else { context.log.leave(NOTICE, `${$grn(_success)} ${spec.name} ${$ms(ms)}`); } }); execution.on("spec:fail", (spec, ms, { number }) => { context.log.leave( ERROR, `${$red(_failure)} ${spec.name} ${$ms(ms)} ${$gry("[")}${$red("failed")}${$gry("|")}${$red(`${number}`)}${$gry("]")}` ); }); execution.on("hook:fail", (hook, ms, { number }) => { context.log.error(`${$red(_failure)} Hook "${hook.name}" ${$ms(ms)} ${$gry("[")}${$red("failed")}${$gry("|")}${$red(`${number}`)}${$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(` ${$gry(_details)} `) + $wht(source.name); context.log.enter(ERROR, `${$gry("[")}${$red(number)}${$gry("]:")} ${details}`); dumpError(context.log, error, genericErrorDiffs); context.log.leave(); } if (summary) { context.log.notice(""); context.log.notice($wht("Test execution summary:")); records.forEach((record) => dumpRecords(context.log, record)); } const totals = [`${passed} ${$gry("passed")}`]; if (skipped) totals.push(`${skipped} ${$gry("skipped")}`); if (failed) totals.push(`${failed} ${$gry("failed")}`); if (failures.length) totals.push(`${failures.length} ${$gry("total failures")}`); const epilogue = `${$gry("(")}${totals.join($gry(", "))}${$gry(")")}`; const message = `Ran ${smsg} from ${fmsg} ${epilogue} ${$ms(time)}`; if (failures.length) { context.log.error(message); throw new BuildFailure(); } else if (suite.flag === "only") { context.log.error(""); context.log.error(message); throw new 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(NOTICE, `${$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(`${$ylw(_success)} ${record.name} ${$ms(record.ms, $ylw("slow"))}`); } else { log2.notice(`${$grn(_success)} ${record.name} ${$ms(record.ms)}`); } break; case "skipped": log2.notice(`${$ylw(_pending)} ${record.name} ${$ms(record.ms, $ylw("skipped"))}`); break; case "failed": log2.notice( `${$red(_failure)} ${record.name} ${$ms(record.ms)} ${$gry("[")}${$red("failed")}${$gry("|")}${$red(`${record.failure}`)}${$gry("]")}` ); break; } } else if (record.type === "hook") { log2.error(`${$red(_failure)} Hook "${record.name}" ${$gry("[")}${$red("failed")}${$gry("|")}${$red(`${record.failure}`)}${$gry("]")}`); } } function dumpError(log2, error, genericErrorDiffs) { if (error instanceof ExpectationError) { log2.enter(ERROR, `${$gry("Expectation Error:")} ${$red(error.message)}`); githubAnnotation({ type: "error", title: "Expectation Error" }, error.message); try { dumpProps(log2, 17, error); dumpStack(log2, error); if (error.diff) printDiff(log2, error.diff); } finally { log2.error(""); log2.leave(); } } else if (error instanceof AssertionError) { const [message = "Unknown Error", ...lines] = error.message.split("\n"); log2.enter(ERROR, `${$gry("Assertion Error:")} ${$red(message)}`); 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); printDiff(log2, 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 BuildFailure ? "Build Failure" : "Unknown Error"); const string = Object.getPrototypeOf(error)?.constructor?.name || "Error"; const type = string === "AssertionError" ? `${$gry("Assertion Error")}: ` : string === "Error" ? "" : `${$gry(string)}: `; log2.enter(ERROR, `${type}${$red(message)}`); githubAnnotation({ type: "error", title: string }, message); try { dumpProps(log2, type.length, error); dumpStack(log2, error); if (genericErrorDiffs && ("actual" in error || "expected" in error)) { printDiff(log2, diff(error.actual, error.expected)); } } finally { log2.error(""); log2.leave(); } } else { log2.error($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 : stringifyValue(value); log2.error($gry(`${k}:`.padStart(pad - 1)), $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)); } export { Test }; //# sourceMappingURL=test.mjs.map