UNPKG

@augment-vir/assert

Version:

A collection of assertions for test and production code alike.

485 lines (484 loc) 20.1 kB
import { stringify, } from '@augment-vir/core'; import { AssertionError } from '../../augments/assertion.error.js'; import { createWaitUntil } from '../../guard-types/wait-until-function.js'; const assertions = { /** * Asserts that two objects are deeply equal by checking only their top-level values for strict * (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. * * Type guards the first value. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.entriesEqual({a: 'a'}, {a: 'a'}); // passes * * assert.entriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // fails * * const bExample = {b: 'b'}; * assert.entriesEqual({a: bExample}, {a: bExample}); // passes * ``` * * @throws {@link AssertionError} If both inputs are not equal. * @see * - {@link assert.notEntriesEqual} : the opposite assertion. * - {@link assert.jsonEquals} : another deep equality assertion. * - {@link assert.deepEquals} : the most thorough (but also slow) deep equality assertion. */ entriesEqual(actual, expected, failureMessage) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!actual || typeof actual !== 'object') { throw new AssertionError(`${stringify(actual)} is not an object.`, failureMessage); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (!expected || typeof expected !== 'object') { throw new AssertionError(`${stringify(expected)} is not an object.`, failureMessage); } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); allKeys.forEach((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; if (actualValue !== expectedValue) { throw new AssertionError(`Entries are not equal at key '${String(key)}'.`, failureMessage); } }); }, /** * Asserts that two objects are _not_ deeply equal by checking only their top-level values for * strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. * * Performs no type guarding. * * @example * * ```ts * import {assert} from '@augment-vir/assert'; * * assert.notEntriesEqual({a: 'a'}, {a: 'a'}); // fails * * assert.notEntriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // passes * * const bExample = {b: 'b'}; * assert.notEntriesEqual({a: bExample}, {a: bExample}); // fails * ``` * * @throws {@link AssertionError} If both inputs are equal. * @see * - {@link assert.entriesEqual} : the opposite assertion. * - {@link assert.notJsonEquals} : another not deep equality assertion. * - {@link assert.notDeepEquals} : the most thorough (but also slow) not deep equality assertion. */ notEntriesEqual(actual, expected, failureMessage) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!actual || typeof actual !== 'object' || !expected || typeof expected !== 'object') { return; } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); const valid = allKeys.some((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; return actualValue !== expectedValue; }); if (!valid) { throw new AssertionError('Entries are equal.', failureMessage); } }, }; export const entryEqualityGuards = { assert: assertions, check: { /** * Checks that two objects are deeply equal by checking only their top-level values for * strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. * * Type guards the first value. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.entriesEqual({a: 'a'}, {a: 'a'}); // true * * check.entriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // false * * const bExample = {b: 'b'}; * check.entriesEqual({a: bExample}, {a: bExample}); // true * ``` * * @see * - {@link check.notEntriesEqual} : the opposite check. * - {@link check.jsonEquals} : another deep equality check. * - {@link check.deepEquals} : the most thorough (but also slow) deep equality check. */ entriesEqual(actual, expected) { if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !actual || typeof actual !== 'object' || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !expected || typeof expected !== 'object') { return false; } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); return allKeys.every((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; return actualValue === expectedValue; }); }, /** * Checks that two objects are _not_ deeply equal by checking only their top-level values * for strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. * * Performs no type guarding. * * @example * * ```ts * import {check} from '@augment-vir/assert'; * * check.notEntriesEqual({a: 'a'}, {a: 'a'}); // false * * check.notEntriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // true * * const bExample = {b: 'b'}; * check.notEntriesEqual({a: bExample}, {a: bExample}); // false * ``` * * @see * - {@link check.entriesEqual} : the opposite check. * - {@link check.notJsonEquals} : another not deep equality check. * - {@link check.notDeepEquals} : the most thorough (but also slow) not deep equality check. */ notEntriesEqual(actual, expected) { if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !actual || typeof actual !== 'object' || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !expected || typeof expected !== 'object') { return true; } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); return allKeys.some((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; return actualValue !== expectedValue; }); }, }, assertWrap: { /** * Asserts that two objects are deeply equal by checking only their top-level values for * strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality and, if so, returns the first object. * * Type guards the first value. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.entriesEqual({a: 'a'}, {a: 'a'}); // returns `{a: 'a'}` * * assertWrap.entriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // throws an error * * const bExample = {b: 'b'}; * assertWrap.entriesEqual({a: bExample}, {a: bExample}); // returns `{a: {b: 'b'}}` * ``` * * @returns The first input if the assertion passes. * @throws {@link AssertionError} If both inputs are not equal. * @see * - {@link assertWrap.notEntriesEqual} : the opposite assertion. * - {@link assertWrap.jsonEquals} : another deep equality assertion. * - {@link assertWrap.deepEquals} : the most thorough (but also slow) deep equality assertion. */ entriesEqual(actual, expected, failureMessage) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (!actual || typeof actual !== 'object') { throw new AssertionError(`${stringify(actual)} is not an object.`, failureMessage); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition } else if (!expected || typeof expected !== 'object') { throw new AssertionError(`${stringify(expected)} is not an object.`, failureMessage); } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); allKeys.forEach((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; if (actualValue !== expectedValue) { throw new AssertionError(`Entries are not equal at key '${String(key)}'.`, failureMessage); } }); return actual; }, /** * Asserts that two objects are _not_ deeply equal by checking only their top-level values * for strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality and, if so, returns the first object. * * Performs no type guarding. * * @example * * ```ts * import {assertWrap} from '@augment-vir/assert'; * * assertWrap.notEntriesEqual({a: 'a'}, {a: 'a'}); // throws an error * * assertWrap.notEntriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // returns `{a: {b: 'b'}}` * * const bExample = {b: 'b'}; * assertWrap.notEntriesEqual({a: bExample}, {a: bExample}); // throws an error * ``` * * @returns The first input if the assertion passes. * @throws {@link AssertionError} If both inputs are equal. * @see * - {@link assertWrap.entriesEqual} : the opposite assertion. * - {@link assertWrap.notJsonEquals} : another not deep equality assertion. * - {@link assertWrap.notDeepEquals} : the most thorough (but also slow) not deep equality assertion. */ notEntriesEqual(actual, expected, failureMessage) { if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !actual || typeof actual !== 'object' || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !expected || typeof expected !== 'object') { return actual; } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); const valid = allKeys.some((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; return actualValue !== expectedValue; }); if (valid) { return actual; } else { throw new AssertionError('Entries are equal.', failureMessage); } }, }, checkWrap: { /** * Checks that two objects are deeply equal by checking only their top-level values for * strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. If the check passes the first object is returned. If not, `undefined` is * returned. * * Type guards the first value. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.entriesEqual({a: 'a'}, {a: 'a'}); // returns `{a: 'a'}` * * checkWrap.entriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // returns `undefined` * * const bExample = {b: 'b'}; * checkWrap.entriesEqual({a: bExample}, {a: bExample}); // returns `{a: {b: 'b'}}` * ``` * * @returns The first input if the assertion passes, otherwise `undefined`. * @see * - {@link checkWrap.notEntriesEqual} : the opposite check. * - {@link checkWrap.jsonEquals} : another deep equality check. * - {@link checkWrap.deepEquals} : the most thorough (but also slow) deep equality check. */ entriesEqual(actual, expected) { if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !actual || typeof actual !== 'object' || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !expected || typeof expected !== 'object') { return undefined; } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); const valid = allKeys.every((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; return actualValue === expectedValue; }); if (valid) { return actual; } else { return undefined; } }, /** * Checks that two objects are _not_ deeply equal by checking only their top-level values * for strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. If the check passes the first object is returned. If not, `undefined` is * returned. * * Performs no type guarding. * * @example * * ```ts * import {checkWrap} from '@augment-vir/assert'; * * checkWrap.notEntriesEqual({a: 'a'}, {a: 'a'}); // returns `undefined` * * checkWrap.notEntriesEqual({a: {b: 'b'}}, {a: {b: 'b'}}); // returns `{a: {b: 'b'}}` * * const bExample = {b: 'b'}; * checkWrap.notEntriesEqual({a: bExample}, {a: bExample}); // returns `undefined` * ``` * * @returns The first input if the assertion passes, otherwise `undefined`. * @see * - {@link checkWrap.entriesEqual} : the opposite check. * - {@link checkWrap.notJsonEquals} : another not deep equality check. * - {@link checkWrap.notDeepEquals} : the most thorough (but also slow) not deep equality check. */ notEntriesEqual(actual, expected) { if ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !actual || typeof actual !== 'object' || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition !expected || typeof expected !== 'object') { return actual; } const allKeys = Array.from(new Set([ ...Reflect.ownKeys(actual), ...Reflect.ownKeys(expected), ])); const valid = allKeys.some((key) => { const actualValue = actual[key]; const expectedValue = expected[key]; return actualValue !== expectedValue; }); if (valid) { return actual; } else { return undefined; } }, }, waitUntil: { /** * Repeatedly calls a callback until its output is deeply equal to the first input by * checking only their top-level values for strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. Once the callback output passes, it is returned. If the attempts time out, an * error is thrown. * * Type guards the first value. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.entriesEqual({a: 'a'}, () => { * return {a: 'a'}; * }); // returns `{a: 'a'}` * * await waitUntil.entriesEqual({a: {b: 'b'}}, () => { * return {a: {b: 'b'}}; * }); // throws an error * * const bExample = {b: 'b'}; * await waitUntil.entriesEqual({a: bExample}, () => { * return {a: bExample}; * }); // returns `{a: {b: 'b'}}` * ``` * * @throws {@link AssertionError} On timeout. * @see * - {@link waitUntil.notEntriesEqual} : the opposite assertion. * - {@link waitUntil.jsonEquals} : another deep equality assertion. * - {@link waitUntil.deepEquals} : the most thorough (but also slow) deep equality assertion. */ entriesEqual: createWaitUntil(assertions.entriesEqual), /** * Repeatedly calls a callback until its output is _not_ deeply equal to the first input by * checking only their top-level values for strict (non-deep, reference, using * [`===`](https://developer.mozilla.org/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using)) * equality. Once the callback output passes, it is returned. If the attempts time out, an * error is thrown. * * Performs no type guarding. * * @example * * ```ts * import {waitUntil} from '@augment-vir/assert'; * * await waitUntil.notEntriesEqual({a: 'a'}, () => { * return {a: 'a'}; * }); // throws an error * * await waitUntil.notEntriesEqual({a: {b: 'b'}}, () => { * return {a: {b: 'b'}}; * }); // returns `{a: {b: 'b'}}` * * const bExample = {b: 'b'}; * await waitUntil.notEntriesEqual({a: bExample}, () => { * return {a: bExample}; * }); // throws an error * ``` * * @returns The callback output once it passes. * @throws {@link AssertionError} If both inputs are equal. * @see * - {@link waitUntil.entriesEqual} : the opposite assertion. * - {@link waitUntil.notJsonEquals} : another not deep equality assertion. * - {@link waitUntil.notDeepEquals} : the most thorough (but also slow) not deep equality assertion. */ notEntriesEqual: createWaitUntil(assertions.notEntriesEqual), }, };