@augment-vir/assert
Version:
A collection of assertions for test and production code alike.
851 lines (850 loc) • 27.7 kB
JavaScript
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),
},
};