UNPKG

@plugjs/expect5

Version:

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

234 lines (233 loc) 7.94 kB
// execution/executable.ts import assert from "node:assert"; import { AsyncLocalStorage } from "node:async_hooks"; import { getSingleton } from "@plugjs/plug/utils"; function execute(call, timeout, notify) { return new Promise((resolve) => { let resolved = false; const abort = new AbortController(); const handle = setTimeout(() => { if (resolved) return; const error = new Error(`Timeout of ${timeout} ms reached`); resolve(error); notify?.(error); resolved = true; }, timeout).unref(); void Promise.resolve().then(async () => { try { await call.call(void 0, abort.signal); resolve(void 0); resolved = true; } catch (cause) { const error = cause instanceof Error ? cause : new Error(String(cause)); notify?.(error); resolve(error); resolved = true; } finally { abort.abort("Spec finished"); clearTimeout(handle); } }); }); } var suiteKey = /* @__PURE__ */ Symbol.for("plugjs:expect5:singleton:suiteStorage"); var skipKey = /* @__PURE__ */ Symbol.for("plugjs:expect5:singleton:skipStorage"); var suiteStorage = getSingleton(suiteKey, () => new AsyncLocalStorage()); var skipStorage = getSingleton(skipKey, () => new AsyncLocalStorage()); function getCurrentSuite() { const suite = suiteStorage.getStore(); assert(suite, "No suite found"); return suite; } function skip() { const skipState = skipStorage.getStore(); assert(skipState, 'The "skip" function can only be used in specs or hooks'); skipState.skipped = true; } var suiteMarker = /* @__PURE__ */ Symbol.for("plugjs:expect5:types:Suite"); var Suite = class { constructor(parent, name, call, timeout = 5e3, flag = void 0) { this.parent = parent; this.name = name; this.call = call; this.timeout = timeout; this.flag = flag; } _beforeAll = []; _beforeEach = []; _afterAll = []; _afterEach = []; _suites = []; _specs = []; _children = []; _setup = false; static { this.prototype[suiteMarker] = suiteMarker; } static [Symbol.hasInstance](instance) { return instance && instance[suiteMarker] === suiteMarker; } get specs() { return this._suites.reduce((n, s) => n + s.specs, 0) + this._specs.length; } /** Add a child {@link Suite} to this */ addSuite(suite) { assert.strictEqual(suite.parent, this, "Suite is not a child of this"); this._children.push(suite); this._suites.push(suite); } /** Add a {@link Spec} to this */ addSpec(spec) { assert.strictEqual(spec.parent, this, "Spec is not a child of this"); this._children.push(spec); this._specs.push(spec); } /** Add a _before all_ {@link Hook} to this */ addBeforeAllHook(hook) { assert.strictEqual(hook.parent, this, "Hook is not a child of this"); assert.strictEqual(hook.name, "beforeAll", `Invalid before all hook name "${hook.name}"`); this._beforeAll.push(hook); } /** Add a _before each_ {@link Hook} to this */ addBeforeEachHook(hook) { assert.strictEqual(hook.parent, this, "Hook is not a child of this"); assert.strictEqual(hook.name, "beforeEach", `Invalid before each hook name "${hook.name}"`); this._beforeEach.push(hook); } /** Add a _after all_ {@link Hook} to this */ addAfterAllHook(hook) { assert.strictEqual(hook.parent, this, "Hook is not a child of this"); assert.strictEqual(hook.name, "afterAll", `Invalid after all hook name "${hook.name}"`); this._afterAll.push(hook); } /** Add a _after each_ {@link Hook} to this */ addAfterEachHook(hook) { assert.strictEqual(hook.parent, this, "Hook is not a child of this"); assert.strictEqual(hook.name, "afterEach", `Invalid after each hook name "${hook.name}"`); this._afterEach.push(hook); } /** * Setup this {@link Suite} invoking its main function, then initializing all * children {@link Suite Suites}, and finally normalizing execution flags. */ async setup() { if (this._setup) return; this._setup = true; await suiteStorage.run(this, async () => { const error = await execute(this.call, this.timeout); if (error) throw error; }); if (this.parent) { this._beforeEach.unshift(...this.parent._beforeEach.map((h) => h.clone(this))); this._afterEach.push(...this.parent._afterEach.map((h) => h.clone(this))); } for (const suite of this._suites) { await suite.setup(); } for (const spec of this._specs) { spec.before.push(...this._beforeEach.map((h) => h.clone(spec))); spec.after.push(...this._afterEach.map((h) => h.clone(spec))); } const only = this._children.reduce((o, c) => o || c.flag === "only", false); if (only) { this._children.forEach((c) => c.flag !== "only" && (c.flag = "skip")); this.flag = "only"; } if (this.flag === "only") { this._children.forEach((c) => c.flag !== "skip" && (c.flag = "only")); } for (const child of this._children) { if (child.flag !== "skip") return; } this.flag = "skip"; } /** * Execute this suite, executing all {@link Hook hooks} and children * {@link Spec specs} and {@link Suite suites} */ async execute(executor, skip2 = false) { const { done } = executor.start(this); if (skip2 || this.flag === "skip") { for (const child of this._children) await child.execute(executor, true); return done(); } for (const hook of this._beforeAll) { const failed = await hook.execute(executor); if (failed) { for (const child of this._children) await child.execute(executor, true); return done(); } } for (const child of this._children) await child.execute(executor); for (const hook of this._afterAll) await hook.execute(executor); done(); } }; var specMarker = /* @__PURE__ */ Symbol.for("plugjs:expect5:types:Spec"); var Spec = class { constructor(parent, name, call, timeout = 5e3, flag = void 0) { this.parent = parent; this.name = name; this.call = call; this.timeout = timeout; this.flag = flag; } before = []; after = []; static { this.prototype[specMarker] = specMarker; } static [Symbol.hasInstance](instance) { return instance && instance[specMarker] === specMarker; } /** Execute this spec */ async execute(executor, skip2 = false) { const { done, notify } = executor.start(this); if (skip2 || this.flag == "skip") return done(true); for (const hook of this.before) { const failed = await hook.execute(executor); if (failed) return done(true); } const skipState = { skipped: false }; await skipStorage.run(skipState, () => execute(this.call, this.timeout, notify)); for (const hook of this.after) await hook.execute(executor); return done(skipState.skipped); } }; var hookMarker = /* @__PURE__ */ Symbol.for("plugjs:expect5:types:Hook"); var Hook = class _Hook { constructor(parent, name, call, timeout = 5e3, flag = void 0) { this.parent = parent; this.name = name; this.call = call; this.timeout = timeout; this.flag = flag; } static { this.prototype[hookMarker] = hookMarker; } static [Symbol.hasInstance](instance) { return instance && instance[hookMarker] === hookMarker; } /** Execute this hook */ async execute(executor) { if (this.flag === "skip") return false; const { done, notify } = executor.start(this); const skipState = { skipped: false }; const error = await skipStorage.run(skipState, () => execute(this.call, this.timeout, notify)); done(skipState.skipped); return !!error; } /** Clone this associating it with a new {@link Suite} or {@link Spec} */ clone(parent) { return new _Hook(parent, this.name, this.call, this.timeout, this.flag); } }; export { Hook, Spec, Suite, getCurrentSuite, skip }; //# sourceMappingURL=executable.mjs.map