UNPKG

@augment-vir/assert

Version:

A collection of assertions for test and production code alike.

851 lines (850 loc) 27.7 kB
import { stringify, } from '@augment-vir/core'; import { AssertionError } from '../augments/assertion.error.js'; import { createWaitUntil } from '../guard-types/wait-until-function.js'; const hasKeyAttempts = [ (object, key) => { return key in object; }, (object, key) => { /** This handles cases where the input object can't use `in` directly, like string literals */ return key in object.constructor.prototype; }, ]; function hasKey(parent, key) { return hasKeyAttempts.some((attemptCallback) => { try { return attemptCallback(parent, key); } catch { return false; } }); } const assertions = { /** * Asserts that a key is contained within a parent value. * * Type guards the key. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.isKeyof('a', {a: 0, b: 1}); // passes * assert.isKeyof('c', {a: 0, b: 1}); // fails * ``` * * @throws {@link AssertionError} If the key is not in the parent. * @see * - {@link assert.isNotKeyOf} : the opposite assertion. */ isKeyOf(key, parent, failureMessage) { if (!hasKey(parent, key)) { throw new AssertionError(`'${String(key)}' is not a key of '${stringify(parent)}'.`, failureMessage); } }, /** * Asserts that a key is _not_ contained within a parent value. * * Type guards the key. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.isNotKeyOf('a', {a: 0, b: 1}); // fails * assert.isNotKeyOf('c', {a: 0, b: 1}); // passes * ``` * * @throws {@link AssertionError} If the key is in the parent. * @see * - {@link assert.isKeyOf} : the opposite assertion. */ isNotKeyOf(key, parent, failureMessage) { if (hasKey(parent, key)) { throw new AssertionError(`'${String(key)}' is a key of '${stringify(parent)}'.`, failureMessage); } }, /** * Asserts that a parent value has the key. * * Type guards the parent value. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.hasKey({a: 0, b: 1}, 'a'); // passes * assert.hasKey({a: 0, b: 1}, 'c'); // fails * ``` * * @throws {@link AssertionError} If the parent does not have the key. * @see * - {@link assert.lacksKey} : the opposite assertion. * - {@link assert.hasKeys} : the multi-key assertion. */ hasKey(parent, key, failureMessage) { if (!hasKey(parent, key)) { throw new AssertionError(`'${stringify(parent)}' does not have key '${String(key)}'.`, failureMessage); } }, /** * Asserts that a parent value does not have the key. * * Type guards the parent value. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.lacksKey({a: 0, b: 1}, 'a'); // fails * assert.lacksKey({a: 0, b: 1}, 'c'); // passes * ``` * * @throws {@link AssertionError} If the parent has the key. * @see * - {@link assert.hasKey} : the opposite assertion. * - {@link assert.lacksKeys} : the multi-key assertion. */ lacksKey(parent, key, failureMessage) { if (hasKey(parent, key)) { throw new AssertionError(`'${stringify(parent)}' has key '${String(key)}'.`, failureMessage); } }, /** * Asserts that a parent value has all the keys. * * Type guards the parent value. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.hasKeys({a: 0, b: 1}, [ * 'a', * 'b', * ]); // passes * assert.hasKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // fails * ``` * * @throws {@link AssertionError} If the parent does not have all the keys. * @see * - {@link assert.lacksKeys} : the opposite assertion. * - {@link assert.hasKey} : the single-key assertion. */ hasKeys(parent, keys, failureMessage) { const missingKeys = keys.filter((key) => !hasKey(parent, key)); if (missingKeys.length) { throw new AssertionError(`'${stringify(parent)}' does not have keys '${missingKeys.join(',')}'.`, failureMessage); } }, /** * Asserts that a parent value none of the keys. * * Type guards the parent value. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.lacksKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // fails * assert.lacksKeys({a: 0, b: 1}, [ * 'c', * 'd', * ]); // passes * ``` * * @throws {@link AssertionError} If the parent has any of the keys. * @see * - {@link assert.hasKeys} : the opposite assertion. * - {@link assert.lacksKey} : the single-key assertion. */ lacksKeys(parent, keys, failureMessage) { const existingKeys = keys.filter((key) => hasKey(parent, key)); if (existingKeys.length) { throw new AssertionError(`'${stringify(parent)}' does not lack keys '${existingKeys.join(',')}'.`, failureMessage); } }, }; export const keyGuards = { assert: assertions, check: { /** * Checks that a key is contained within a parent value. * * Type guards the key. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.isKeyof('a', {a: 0, b: 1}); // returns `true` * check.isKeyof('c', {a: 0, b: 1}); // returns `false` * ``` * * @see * - {@link check.isNotKeyOf} : the opposite check. */ isKeyOf(key, parent) { return hasKey(parent, key); }, /** * Checks that a key is _not_ contained within a parent value. * * Type guards the key. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.isNotKeyOf('a', {a: 0, b: 1}); // returns `false` * check.isNotKeyOf('c', {a: 0, b: 1}); // returns `true` * ``` * * @see * - {@link check.isKeyOf} : the opposite check. */ isNotKeyOf(key, parent) { return !hasKey(parent, key); }, /** * Checks that a parent value has the key. * * Type guards the parent value. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.hasKey({a: 0, b: 1}, 'a'); // returns `true` * check.hasKey({a: 0, b: 1}, 'c'); // returns `false` * ``` * * @see * - {@link check.lacksKey} : the opposite check. * - {@link check.hasKeys} : the multi-key check. */ hasKey, /** * Checks that a parent value does not have the key. * * Type guards the parent value. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.lacksKey({a: 0, b: 1}, 'a'); // returns `false` * check.lacksKey({a: 0, b: 1}, 'c'); // returns `true` * ``` * * @see * - {@link check.hasKey} : the opposite check. * - {@link check.lacksKeys} : the multi-key check. */ lacksKey(parent, key) { return !hasKey(parent, key); }, /** * Checks that a parent value has all the keys. * * Type guards the parent value. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.hasKeys({a: 0, b: 1}, [ * 'a', * 'b', * ]); // returns `true` * check.hasKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // returns `false` * ``` * * @see * - {@link check.lacksKeys} : the opposite check. * - {@link check.hasKey} : the single-key check. */ hasKeys(parent, keys) { return keys.every((key) => hasKey(parent, key)); }, /** * Checks that a parent value none of the keys. * * Type guards the parent value. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.lacksKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // returns `false` * check.lacksKeys({a: 0, b: 1}, [ * 'c', * 'd', * ]); // returns `true` * ``` * * @see * - {@link check.hasKeys} : the opposite check. * - {@link check.lacksKey} : the single-key check. */ lacksKeys(parent, keys) { return keys.every((key) => !hasKey(parent, key)); }, }, assertWrap: { /** * Asserts that a key is contained within a parent value. Returns the key if the assertion * passes. * * Type guards the key. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.isKeyof('a', {a: 0, b: 1}); // returns `'a'` * assertWrap.isKeyof('c', {a: 0, b: 1}); // throws an error * ``` * * @returns The key if it is in the parent. * @throws {@link AssertionError} If the key is not in the parent. * @see * - {@link assertWrap.isNotKeyOf} : the opposite assertion. */ isKeyOf(key, parent, failureMessage) { if (!hasKey(parent, key)) { throw new AssertionError(`'${String(key)}' is not a key of '${stringify(parent)}'.`, failureMessage); } return key; }, /** * Asserts that a key is _not_ contained within a parent value. Returns the key if the * assertion passes. * * Type guards the key. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.isNotKeyOf('a', {a: 0, b: 1}); // throws an error * assertWrap.isNotKeyOf('c', {a: 0, b: 1}); // returns `'c'` * ``` * * @returns The key if it is not in the parent. * @throws {@link AssertionError} If the key is in the parent. * @see * - {@link assertWrap.isKeyOf} : the opposite assertion. */ isNotKeyOf(key, parent, failureMessage) { if (hasKey(parent, key)) { throw new AssertionError(`'${String(key)}' is a key of '${stringify(parent)}'.`, failureMessage); } return key; }, /** * Asserts that a parent value has the key. Returns the parent if the assertion passes. * * Type guards the parent value. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.hasKey({a: 0, b: 1}, 'a'); // returns `{a: 0, b: 1}` * assertWrap.hasKey({a: 0, b: 1}, 'c'); // throws an error * ``` * * @returns The parent if it has the key. * @throws {@link AssertionError} If the parent does not have the key. * @see * - {@link assertWrap.lacksKey} : the opposite assertion. * - {@link assertWrap.hasKeys} : the multi-key assertion. */ hasKey(parent, key, failureMessage) { if (!hasKey(parent, key)) { throw new AssertionError(`'${stringify(parent)}' does not have key '${String(key)}'.`, failureMessage); } return parent; }, /** * Asserts that a parent value does not have the key. Returns the parent if the assertion * passes. * * Type guards the parent value. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.lacksKey({a: 0, b: 1}, 'a'); // throws an error * assertWrap.lacksKey({a: 0, b: 1}, 'c'); // returns `{a: 0, b: 1}` * ``` * * @returns The parent if it does not have the key. * @throws {@link AssertionError} If the parent has the key. * @see * - {@link assertWrap.hasKey} : the opposite assertion. * - {@link assertWrap.lacksKeys} : the multi-key assertion. */ lacksKey(parent, key, failureMessage) { if (hasKey(parent, key)) { throw new AssertionError(`'${stringify(parent)}' has key '${String(key)}'.`, failureMessage); } return parent; }, /** * Asserts that a parent value has all the keys. Returns the parent if the assertion passes. * * Type guards the parent value. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.hasKeys({a: 0, b: 1}, [ * 'a', * 'b', * ]); // returns `{a: 0, b: 1}` * assertWrap.hasKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // throws an error * ``` * * @returns The parent if it has all the keys. * @throws {@link AssertionError} If the parent does not have all the keys. * @see * - {@link assertWrap.lacksKeys} : the opposite assertion. * - {@link assertWrap.hasKey} : the single-key assertion. */ hasKeys(parent, keys, failureMessage) { const missingKeys = keys.filter((key) => !hasKey(parent, key)); if (missingKeys.length) { throw new AssertionError(`'${stringify(parent)}' does not have keys '${missingKeys.join(',')}'.`, failureMessage); } return parent; }, /** * Asserts that a parent value none of the keys. Returns the parent if the assertion passes. * * Type guards the parent value. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.lacksKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // throws an error * assertWrap.lacksKeys({a: 0, b: 1}, [ * 'c', * 'd', * ]); // returns `{a: 0, b: 1}` * ``` * * @returns The parent if it does not have any of the keys. * @throws {@link AssertionError} If the parent has any of the keys. * @see * - {@link assertWrap.hasKeys} : the opposite assertion. * - {@link assertWrap.lacksKey} : the single-key assertion. */ lacksKeys(parent, keys, failureMessage) { const existingKeys = keys.filter((key) => hasKey(parent, key)); if (existingKeys.length) { throw new AssertionError(`'${stringify(parent)}' does not lack keys '${existingKeys.join(',')}'.`, failureMessage); } return parent; }, }, checkWrap: { /** * Checks that a key is contained within a parent value. Returns the key if the check * passes, otherwise `undefined`. * * Type guards the key. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.isKeyof('a', {a: 0, b: 1}); // returns `'a'` * checkWrap.isKeyof('c', {a: 0, b: 1}); // returns `undefined` * ``` * * @returns The key if the check passes, otherwise `undefined`. * @see * - {@link checkWrap.isNotKeyOf} : the opposite check. */ isKeyOf(key, parent) { if (hasKey(parent, key)) { return key; } else { return undefined; } }, /** * Checks that a key is _not_ contained within a parent value. Returns the key if the check * passes, otherwise `undefined`. * * Type guards the key. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.isNotKeyOf('a', {a: 0, b: 1}); // returns `undefined` * checkWrap.isNotKeyOf('c', {a: 0, b: 1}); // returns `'c'` * ``` * * @returns The key if the check passes, otherwise `undefined`. * @see * - {@link checkWrap.isKeyOf} : the opposite check. */ isNotKeyOf(key, parent) { if (hasKey(parent, key)) { return undefined; } else { return key; } }, /** * Checks that a parent value has the key. Returns the parent value if the check passes, * otherwise `undefined`. * * Type guards the parent value. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.hasKey({a: 0, b: 1}, 'a'); // returns `{a: 0, b: 1}` * checkWrap.hasKey({a: 0, b: 1}, 'c'); // returns `undefined` * ``` * * @returns The parent value if the check passes, otherwise `undefined`. * @see * - {@link checkWrap.lacksKey} : the opposite check. * - {@link checkWrap.hasKeys} : the multi-key check. */ hasKey(parent, key) { if (hasKey(parent, key)) { return parent; } else { return undefined; } }, /** * Checks that a parent value does not have the key. Returns the parent value if the check * passes, otherwise `undefined`. * * Type guards the parent value. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.lacksKey({a: 0, b: 1}, 'a'); // returns `undefined` * checkWrap.lacksKey({a: 0, b: 1}, 'c'); // returns `{a: 0, b: 1}` * ``` * * @returns The parent value if the check passes, otherwise `undefined`. * @see * - {@link checkWrap.hasKey} : the opposite check. * - {@link checkWrap.lacksKeys} : the multi-key check. */ lacksKey(parent, key) { if (hasKey(parent, key)) { return undefined; } else { return parent; } }, /** * Checks that a parent value has all the keys. Returns the parent value if the check * passes, otherwise `undefined`. * * Type guards the parent value. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.hasKeys({a: 0, b: 1}, [ * 'a', * 'b', * ]); // returns `{a: 0, b: 1}` * checkWrap.hasKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // returns `undefined` * ``` * * @returns The parent value if the check passes, otherwise `undefined`. * @see * - {@link checkWrap.lacksKeys} : the opposite check. * - {@link checkWrap.hasKey} : the single-key check. */ hasKeys(parent, keys) { if (keys.every((key) => hasKey(parent, key))) { return parent; } else { return undefined; } }, /** * Checks that a parent value none of the keys. Returns the parent value if the check * passes, otherwise `undefined`. * * Type guards the parent value. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.lacksKeys({a: 0, b: 1}, [ * 'b', * 'c', * ]); // returns `undefined` * checkWrap.lacksKeys({a: 0, b: 1}, [ * 'c', * 'd', * ]); // returns `{a: 0, b: 1}` * ``` * * @returns The parent value if the check passes, otherwise `undefined`. * @see * - {@link checkWrap.hasKeys} : the opposite check. * - {@link checkWrap.lacksKey} : the single-key check. */ lacksKeys(parent, keys) { if (keys.every((key) => !hasKey(parent, key))) { return parent; } else { return undefined; } }, }, waitUntil: { /** * Repeatedly calls a callback until its output is a key that is contained within the first, * parent value. Once the callback output passes, it is returned. If the attempts time out, * an error is thrown. * * Type guards the key. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.isKeyof({a: 0, b: 1}, () => 'a'); // returns `'a'` * await waitUntil.isKeyof({a: 0, b: 1}, () => 'c'); // throws an error * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.isNotKeyOf} : the opposite assertion. */ isKeyOf: createWaitUntil(assertions.isKeyOf), /** * Repeatedly calls a callback until its output is a key that is _not_ contained within the * first, parent value. Once the callback output passes, it is returned. If the attempts * time out, an error is thrown. * * Type guards the key. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.isKeyof({a: 0, b: 1}, () => 'a'); // throws an error * await waitUntil.isKeyof({a: 0, b: 1}, () => 'c'); // returns `'c'` * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.isNotKeyOf} : the opposite assertion. */ isNotKeyOf: createWaitUntil(assertions.isNotKeyOf), /** * Repeatedly calls a callback until its output is a parent value that has the first, key * input. Once the callback output passes, it is returned. If the attempts time out, an * error is thrown. * * Type guards the parent value. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.hasKey('a', () => { * return {a: 0, b: 1}; * }); // returns `{a: 0, b: 1}` * await waitUntil.hasKey('c', () => { * return {a: 0, b: 1}; * }); // throws an error * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.lacksKey} : the opposite assertion. * - {@link waitUntil.hasKeys} : the multi-key assertion. */ hasKey: createWaitUntil(assertions.hasKey), /** * Repeatedly calls a callback until its output is a parent value that does not have the * first, key input. Once the callback output passes, it is returned. If the attempts time * out, an error is thrown. * * Type guards the parent value. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.hasKey('a', () => { * return {a: 0, b: 1}; * }); // throws an error * await waitUntil.hasKey('c', () => { * return {a: 0, b: 1}; * }); // returns `{a: 0, b: 1}` * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.hasKey} : the opposite assertion. * - {@link waitUntil.lacksKeys} : the multi-key assertion. */ lacksKey: createWaitUntil(assertions.lacksKey), /** * Repeatedly calls a callback until its output is a parent value that has all of the first, * keys input. Once the callback output passes, it is returned. If the attempts time out, an * error is thrown. * * Type guards the parent value. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.hasKeys( * [ * 'a', * 'b', * ], * () => { * return {a: 0, b: 1}; * }, * ); // returns `{a: 0, b: 1}` * await waitUntil.hasKeys( * [ * 'b', * 'c', * ], * () => { * return {a: 0, b: 1}; * }, * ); // throws an error * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.lacksKeys} : the opposite assertion. * - {@link waitUntil.hasKey} : the single-key assertion. */ hasKeys: createWaitUntil(assertions.hasKeys), /** * Repeatedly calls a callback until its output is a parent value that does not have any of * the first, keys input. Once the callback output passes, it is returned. If the attempts * time out, an error is thrown. * * Type guards the parent value. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.hasKeys( * [ * 'a', * 'b', * ], * () => { * return {a: 0, b: 1}; * }, * ); // throws an error * await waitUntil.hasKeys( * [ * 'b', * 'c', * ], * () => { * return {a: 0, b: 1}; * }, * ); // returns `{a: 0, b: 1}` * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.hasKeys} : the opposite assertion. * - {@link waitUntil.lacksKey} : the single-key assertion. */ lacksKeys: createWaitUntil(assertions.lacksKeys), }, };