UNPKG

@oazmi/build-tools

Version:

general deno build tool scripts which I practically use in all of my typescript repos

165 lines (164 loc) 5.93 kB
// Copyright 2018-2024 the Deno authors. MIT license. export async function runTestDefinitions(testDefinitions, options) { const testFailures = []; const hasOnly = testDefinitions.some((d) => d.only); if (hasOnly) { testDefinitions = testDefinitions.filter((d) => d.only); } for (const definition of testDefinitions) { options.process.stdout.write("test " + definition.name + " ..."); if (definition.ignore) { options.process.stdout.write(` ${options.pc.gray("ignored")}\n`); continue; } const context = getTestContext(definition, undefined); let pass = false; try { await definition.fn(context); if (context.hasFailingChild) { testFailures.push({ name: definition.name, err: new Error("Had failing test step."), }); } else { pass = true; } } catch (err) { testFailures.push({ name: definition.name, err }); } const testStepOutput = context.getOutput(); if (testStepOutput.length > 0) { options.process.stdout.write(testStepOutput); } else { options.process.stdout.write(" "); } options.process.stdout.write(getStatusText(pass ? "ok" : "fail")); options.process.stdout.write("\n"); } if (testFailures.length > 0) { options.process.stdout.write("\nFAILURES"); for (const failure of testFailures) { options.process.stdout.write("\n\n"); options.process.stdout.write(failure.name + "\n"); options.process.stdout.write(indentText((failure.err?.stack ?? failure.err).toString(), 1)); } options.process.exit(1); } else if (hasOnly) { options.process.stdout.write('error: Test failed because the "only" option was used.\n'); options.process.exit(1); } function getTestContext(definition, parent) { return { name: definition.name, parent, origin: options.origin, /** @type {any} */ err: undefined, status: "ok", children: [], get hasFailingChild() { return this.children.some((c) => c.status === "fail" || c.status === "pending"); }, getOutput() { let output = ""; if (this.parent) { output += "test " + this.name + " ..."; } if (this.children.length > 0) { output += "\n" + this.children.map((c) => indentText(c.getOutput(), 1)).join("\n") + "\n"; } else if (!this.err) { output += " "; } if (this.parent && this.err) { output += "\n"; } if (this.err) { output += indentText((this.err.stack ?? this.err).toString(), 1); if (this.parent) { output += "\n"; } } if (this.parent) { output += getStatusText(this.status); } return output; }, async step(nameOrTestDefinition, fn) { const definition = getDefinition(); const context = getTestContext(definition, this); context.status = "pending"; this.children.push(context); if (definition.ignore) { context.status = "ignored"; return false; } try { await definition.fn(context); context.status = "ok"; if (context.hasFailingChild) { context.status = "fail"; return false; } return true; } catch (err) { context.status = "fail"; context.err = err; return false; } /** @returns {TestDefinition} */ function getDefinition() { if (typeof nameOrTestDefinition === "string") { if (!(fn instanceof Function)) { throw new TypeError("Expected function for second argument."); } return { name: nameOrTestDefinition, fn, }; } else if (typeof nameOrTestDefinition === "object") { return nameOrTestDefinition; } else { throw new TypeError("Expected a test definition or name and function."); } } }, }; } function getStatusText(status) { switch (status) { case "ok": return options.pc.green(status); case "fail": case "pending": return options.pc.red(status); case "ignored": return options.pc.gray(status); default: { const _assertNever = status; return status; } } } function indentText(text, indentLevel) { if (text === undefined) { text = "[undefined]"; } else if (text === null) { text = "[null]"; } else { text = text.toString(); } return text.split(/\r?\n/) .map((line) => " ".repeat(indentLevel) + line) .join("\n"); } }