UNPKG

assert-helpers

Version:

Common utilities and helpers to make testing assertions easier

610 lines (609 loc) 21.9 kB
"use strict"; /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.wait = wait; exports.isNode = isNode; exports.isTTY = isTTY; exports.bool = bool; exports.useColors = useColors; exports.color = color; exports.inspect = inspect; exports.log = log; exports.logComparison = logComparison; exports.equal = equal; exports.gte = gte; exports.lte = lte; exports.gt = gt; exports.lt = lt; exports.undef = undef; exports.nullish = nullish; exports.deepEqual = deepEqual; exports.contains = contains; exports.notContains = notContains; exports.errorEqual = errorEqual; exports.returnViaCallback = returnViaCallback; exports.completeViaCallback = completeViaCallback; exports.errorViaCallback = errorViaCallback; exports.returnErrorViaCallback = returnErrorViaCallback; exports.throwErrorViaCallback = throwErrorViaCallback; exports.expectViaCallback = expectViaCallback; exports.expectErrorViaCallback = expectErrorViaCallback; exports.expectThrowViaFunction = expectThrowViaFunction; exports.expectErrorViaFunction = expectErrorViaFunction; exports.expectFunctionToThrow = expectFunctionToThrow; // Polyfill for older node versions if (Array.prototype.includes == null) { // Array.prototype.includes missing on Node.js v4, first added in Node.js v6 // eslint-disable-next-line no-extend-native Array.prototype.includes = function (searchElement) { return this.indexOf(searchElement) !== -1; }; } // String.prototype.includes included on Node.js v4 // Import const assert_1 = require("assert"); const util_1 = require("util"); const ansi = __importStar(require("@bevry/ansi")); const errlop_1 = __importDefault(require("errlop")); const process_1 = require("process"); /** * Alias for setTimeout with paramaters reversed. * @param delay - The delay in milliseconds before executing the function * @param fn - The function to execute after the delay * @returns The timer ID returned by setTimeout */ function wait(delay, fn) { return setTimeout(fn, delay); } /** * Whether or not we are running in the node environment * @returns True if running in Node.js, false otherwise */ function isNode() { return Boolean(process_1.versions && process_1.versions.node); } /** * Whether or not stdout and stderr are interactive. * @returns True if both stdout and stderr are TTY, false otherwise */ function isTTY() { return isNode() && (process_1.stdout === null || process_1.stdout === void 0 ? void 0 : process_1.stdout.isTTY) === true && (process_1.stderr === null || process_1.stderr === void 0 ? void 0 : process_1.stderr.isTTY) === true; } /** * Convert a value to its boolean equivalent * @param value - The value to convert to boolean * @returns The boolean equivalent of the value, or null if undetermined */ function bool(value) { // node.js 15 apparently sets COLOR to 0 (is TTY) 1 (not TTY) https://github.com/bevry/assert-helpers/commit/593c40a7211f460532077e67a38a452b707b4f9c // however I was unable to reproduce in 2025 via console.log(process.env.COLOR) which outputs yes, which is what my shell env has set it to const string = String(value).toLowerCase(); if (['no', 'n', 'false', '0'].includes(string)) { return false; } if (['yes', 'y', 'true', '1'].includes(string)) { return true; } // if (['null', ''].includes(string)) { // return null //} // otherwise, it's something unknown, discard return null; } /** * Whether or not colors are desired on this environment * @returns True if colors should be used, false otherwise */ function useColors() { var _a, _b; // handle strong technical capability if (!isNode()) return false; // disabled or enabled by strong user preference if (process_1.argv.includes('--no-colors') || process_1.argv.includes('--no-color')) return false; if (process_1.argv.includes('--colors') || process_1.argv.includes('--color')) return false; // @todo support [--[no-]color[s]=<truthy/falsey>] arguments // handle disabled by soft technical capability // if (!isTTY()) return false <-- despite this making sense, for some reason, the wide convention is for env vars to take preference over TTY detection, probably as many want colors when piping // handle soft user preference return (_b = (_a = bool(process_1.env.COLOR)) !== null && _a !== void 0 ? _a : bool(process_1.env.COLORS)) !== null && _b !== void 0 ? _b : isTTY(); } /** * Applies the color to the value if desired * @param value - The value to colorize * @param color - The color function to apply * @returns The colorized string if colors are enabled, otherwise the string value */ function color(value, color) { return useColors() ? color(value) : String(value); } /** * Checks to see if a value is an object * https://github.com/bevry/typechecker/blob/69008d42927749d7e21cfe9816e478dd8d15ab88/source/index.js#L22-L30 * @param value - The value to check * @returns True if the value is an object (but not null), false otherwise */ function isObject(value) { // null is object, hence the extra check return value !== null && typeof value === 'object'; } /** * Return a stringified version of the value with indentation and colors where applicable. * Colors will be applied if the environment supports it (--no-colors not present and TTY). * For the available options, refer to https://nodejs.org/dist/latest-v14.x/docs/api/util.html#util_util_inspect_object_options for Node.js * @param value - The value to inspect and stringify * @param opts - Additional options for the inspection * @returns The stringified and optionally colorized representation of the value */ function inspect(value, opts = {}) { // If the terminal supports colours, and the user hasn't specified, then default to a sensible default const colors = useColors(); const depth = 50; // Inspect and return using our defaults return (0, util_1.inspect)(value, Object.assign({ colors, depth }, opts)); } /** * Log the inspected values of each of the arguments to stdout * @param args - The values to log */ function log(...args) { if (isNode() && process_1.env.ASSERT_SILENCE) return; for (let i = 0; i < args.length; ++i) { console.log(inspect(args[i])); } } /** * Output a comparison of the failed result to stderr * @param actual - The actual value that was received * @param expected - The expected value * @param error - The error that occurred during comparison */ function logComparison(actual, expected, error) { if (isNode() && process_1.env.ASSERT_SILENCE) return; const lines = [ '------------------------------------', 'Comparison Error:', color(error.stack || error.message || error, ansi.green), '', ]; lines.push('Comparison Actual:', inspect(actual), '', 'Comparison Expected:', inspect(expected), '------------------------------------'); // Work for node if (isNode() && process_1.stderr) { process_1.stderr.write(lines.join('\n') + '\n'); } // Work for browsers else { console.log(lines.join('\n')); } } /** * Same as assert.strictEqual in that it performs a strict equals check, but if a failure occurs it will output detailed information * @param actual - The actual value to compare * @param expected - The expected value to compare against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function equal(actual, expected, testName = 'equal assertion', next) { try { (0, assert_1.strictEqual)(actual, expected, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Is greater than or equal to * @param actual - The actual value to compare * @param expected - The expected value to compare against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function gte(actual, expected, testName = 'is greater than or equal to assertion', next) { try { (0, assert_1.strictEqual)(actual >= expected, true, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Is less than or equal to * @param actual - The actual value to compare * @param expected - The expected value to compare against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function lte(actual, expected, testName = 'is less than or equal to assertion', next) { try { (0, assert_1.strictEqual)(actual <= expected, true, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Is greater than * @param actual - The actual value to compare * @param expected - The expected value to compare against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function gt(actual, expected, testName = 'is greater than assertion', next) { try { (0, assert_1.strictEqual)(actual > expected, true, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Is less than * @param actual - The actual value to compare * @param expected - The expected value to compare against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function lt(actual, expected, testName = 'is less than assertion', next) { try { (0, assert_1.strictEqual)(actual < expected, true, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Ensure what is passed is undefined, otherwise fail and output what it is * @param actual - The actual value to check * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function undef(actual, testName = 'undef assertion', next) { try { (0, assert_1.strictEqual)(typeof actual, 'undefined', testName); } catch (checkError) { logComparison(actual, 'undefined', checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Ensure what is passed is undefined or null, otherwise fail and output what it is * @param actual - The actual value to check * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function nullish(actual, testName = 'nullish assertion', next) { try { (0, assert_1.strictEqual)(typeof actual, 'undefined', testName); } catch (e1) { try { (0, assert_1.strictEqual)(actual, null, testName); } catch (e2) { const error = new errlop_1.default(e2, e1); logComparison(actual, 'nullish', error); if (next) { next(error); return; } else { throw error; } } } if (next) next(); } /** * Same as assert.deepStrictEqual in that it performs a deep strict equals check, but if a failure occurs it will output detailed information * @param actual - The actual value to compare * @param expected - The expected value to compare against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function deepEqual(actual, expected, testName = 'deep equal assertion', next) { try { (0, assert_1.deepStrictEqual)(actual, expected, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Checks to see if the actual result contains the expected result . * @param actual - The actual value to check for containment * @param expected - The expected value that should be contained * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function contains(actual, expected, testName = 'contains assertion', next) { if (testName == null) testName = `Expected [${actual}] to contain [${expected}]`; try { (0, assert_1.ok)(actual.includes(expected) === true, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Checks to see if the actual result does not contain the expected result . * @param actual - The actual value to check for non-containment * @param expected - The expected value that should not be contained * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function notContains(actual, expected, testName = 'does not contain assertion', next) { if (testName == null) testName = `Expected [${actual}] to not contain [${expected}]`; try { (0, assert_1.ok)(actual.includes(expected) === false, testName); } catch (checkError) { logComparison(actual, expected, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Checks to see if an error was as expected, if a failure occurs it will output detailed information * @param actualError - The actual error that was thrown * @param expectedError - The expected error to match against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function errorEqual(actualError, expectedError, testName = 'error equal assertion', next) { try { // needs to work with errlop codes, hence usage of stack, as errlop tostring doesn't include code for now, unfortunately if (!actualError || !expectedError) { equal(actualError || '', expectedError || '', testName + ' (empty check)'); } else if (actualError.code === ((expectedError === null || expectedError === void 0 ? void 0 : expectedError.code) || expectedError)) { equal(actualError.code, (expectedError === null || expectedError === void 0 ? void 0 : expectedError.code) || expectedError, testName + ' (.code check)'); } else { contains(actualError.toString(), expectedError.toString(), testName + ' (contains check)'); } } catch (checkError) { logComparison(actualError, expectedError, checkError); if (next) { next(checkError); return; } else { throw checkError; } } if (next) next(); } /** * Generate a callback that will return the specified value. * @param value - The value to return from the callback * @returns A function that returns the specified value */ function returnViaCallback(value) { return function () { return value; }; } /** * Generate a callback that will receive a completion callback whcih it will call with the specified result after the specified delay. * @param value - The value to pass to the completion callback * @param delay - The delay in milliseconds before calling the completion callback * @returns A function that takes a completion callback and calls it with the value after the delay */ function completeViaCallback(value, delay = 100) { return function (complete) { wait(delay, function () { complete(null, value); }); }; } /** * Generate a callback that will receive a completion callback which it will call with the passed error after the specified delay. * @param error - The error to pass to the completion callback * @param delay - The delay in milliseconds before calling the completion callback * @returns A function that takes a completion callback and calls it with the error after the delay */ function errorViaCallback(error, delay = 100) { return function (complete) { wait(delay, function () { complete(error); }); }; } /** * Generate a callback that return an error instance with the specified message/error. * @param error - The error message or Error instance to return * @returns A function that returns an Error instance */ function returnErrorViaCallback(error = 'an error occurred') { return function () { if (error instanceof Error) { return error; } else { return new Error(error); } }; } /** * Generate a callback that throw an error instance with the specified message/error. * @param error - The error message or Error instance to throw * @returns A function that throws an Error instance */ function throwErrorViaCallback(error = 'an error occurred') { return function () { if (error instanceof Error) { throw error; } else { throw new Error(error); } }; } /** * Generate a callback that will check the arguments it received with the arguments specified, if a failure occurs it will output detailed information. * @param expected - The expected arguments to compare against * @returns A function that compares received arguments with expected arguments */ function expectViaCallback(...expected) { return (...actual) => deepEqual(actual, expected); } /** * Generate a callback that will check its error (the actual error) against the passed error (the expected error). * If a failure occurs it will output detailed information. * @param expected - The expected error to match against * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error * @returns A function that compares an actual error with the expected error */ function expectErrorViaCallback(expected, testName = 'expect error via callback assertion', next) { return (actual) => errorEqual(actual, expected, testName, next); } /** * Expect the passed function to throw an error at some point. * @param expected - The expected error to match against * @param fn - The function that should throw an error * @param testName - The name of the test for error reporting * @param next - Optional callback to call with any error */ function expectThrowViaFunction(expected, fn, testName = 'expect error via function assertion', next) { let actual = null; try { fn(); } catch (error) { actual = error; } errorEqual(actual, expected, testName, next); } /** @deprecated Use {@link expectErrorViaFunction} instead */ function expectErrorViaFunction() { throw new Error('expectErrorViaFunction has been deprecated, use expectThrowViaFunction instead'); } /** @deprecated Use {@link expectErrorViaFunction} instead */ function expectFunctionToThrow() { throw new Error('expectFunctionToThrow has been deprecated, use expectThrowViaFunction instead'); }