UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

144 lines (124 loc) 3.46 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/assert/calltracker.js import { codes as __codes__ } from "nstdlib/lib/internal/errors"; import * as AssertionError from "nstdlib/lib/internal/assert/assertion_error"; import { validateUint32 } from "nstdlib/lib/internal/validators"; const { ERR_INVALID_ARG_VALUE, ERR_UNAVAILABLE_DURING_EXIT } = __codes__; const noop = Function.prototype; class CallTrackerContext { #expected; #calls; #name; #stackTrace; constructor({ expected, stackTrace, name }) { this.#calls = []; this.#expected = expected; this.#stackTrace = stackTrace; this.#name = name; } track(thisArg, args) { const argsClone = Object.freeze(Array.prototype.slice.call(args)); Array.prototype.push.call( this.#calls, Object.freeze({ thisArg, arguments: argsClone }), ); } get delta() { return this.#calls.length - this.#expected; } reset() { this.#calls = []; } getCalls() { return Object.freeze(Array.prototype.slice.call(this.#calls)); } report() { if (this.delta !== 0) { const message = `Expected the ${this.#name} function to be ` + `executed ${this.#expected} time(s) but was ` + `executed ${this.#calls.length} time(s).`; return { message, actual: this.#calls.length, expected: this.#expected, operator: this.#name, stack: this.#stackTrace, }; } } } class CallTracker { #callChecks = new Set(); #trackedFunctions = new WeakMap(); #getTrackedFunction(tracked) { if (!this.#trackedFunctions.has(tracked)) { throw new ERR_INVALID_ARG_VALUE( "tracked", tracked, "is not a tracked function", ); } return this.#trackedFunctions.get(tracked); } reset(tracked) { if (tracked === undefined) { this.#callChecks.forEach((check) => check.reset()); return; } this.#getTrackedFunction(tracked).reset(); } getCalls(tracked) { return this.#getTrackedFunction(tracked).getCalls(); } calls(fn, expected = 1) { if (process._exiting) throw new ERR_UNAVAILABLE_DURING_EXIT(); if (typeof fn === "number") { expected = fn; fn = noop; } else if (fn === undefined) { fn = noop; } validateUint32(expected, "expected", true); const context = new CallTrackerContext({ expected, // eslint-disable-next-line no-restricted-syntax stackTrace: new Error(), name: fn.name || "calls", }); const tracked = new Proxy(fn, { __proto__: null, apply(fn, thisArg, argList) { context.track(thisArg, argList); return ReflectApply(fn, thisArg, argList); }, }); this.#callChecks.add(context); this.#trackedFunctions.set(tracked, context); return tracked; } report() { const errors = []; for (const context of this.#callChecks) { const message = context.report(); if (message !== undefined) { Array.prototype.push.call(errors, message); } } return errors; } verify() { const errors = this.report(); if (errors.length === 0) { return; } const message = errors.length === 1 ? errors[0].message : "Functions were not called the expected number of times"; throw new AssertionError({ message, details: errors, }); } } export default CallTracker;