UNPKG

@assertive-ts/core

Version:

A type-safe fluent assertion library

496 lines 16.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Assertion = void 0; const tslib_1 = require("tslib"); const es6_1 = tslib_1.__importDefault(require("fast-deep-equal/es6")); const UnsupportedOperationError_1 = require("./errors/UnsupportedOperationError"); const guards_1 = require("./helpers/guards"); const messages_1 = require("./helpers/messages"); const assert_1 = require("assert"); /** * Base class for all assertions. * * @param T the type of the `actual` value */ class Assertion { constructor(actual) { this.actual = actual; this.inverted = false; this.not = new Proxy(this, { get: this.proxyInverter(true) }); } /** * A convenience method to normalize the assertion instance. If it was * inverted with `.not`, it'll return it back to the previous non-inverted * state. Otherwise, it returns the same instance. * * @returns the normalized assertion instance */ normalized() { return this.inverted ? new Proxy(this, { get: this.proxyInverter(false) }) : this; } /** * A convenience method to execute the assertion. The inversion logic for * `.not` is already embedded in this method, so this should always be used * in assertions to keep the negation system working * * @param options the execution options for the assertion * @returns the Assertion instance if no error was thrown */ execute(options) { const { assertWhen, error, invertedError } = options; if (!assertWhen && !this.inverted) { throw error; } if (assertWhen && this.inverted) { throw invertedError; } return this.normalized(); } /** * Check if the value matches the given predicate. * * @param matcher a matcher predicate * @returns the assertion instance */ toMatch(matcher) { const error = new assert_1.AssertionError({ actual: this.actual, message: "Expected matcher predicate to return true", }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected matcher predicate NOT to return true", }); return this.execute({ assertWhen: matcher(this.actual), error, invertedError, }); } /** * Check if the value exists. This means that the value should be neither * `null` nor `undefined`. * * @example * ``` * expect(planetEarth).toExist(); * ``` * * @returns the assertion instance */ toExist() { const error = new assert_1.AssertionError({ actual: this.actual, message: `Expected value to exist, but it was <${(0, messages_1.prettify)(this.actual)}>`, }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: `Expected value to NOT exist, but it was <${(0, messages_1.prettify)(this.actual)}>`, }); return this.execute({ assertWhen: this.actual !== undefined && this.actual !== null, error, invertedError, }); } /** * Check if the value is `undefined` * * @example * ``` * expect(myUndefinedValue).toBeUndefined() * ``` * * @returns the assertion instance */ toBeUndefined() { const error = new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> to be undefined`, }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected the value NOT to be undefined", }); return this.execute({ assertWhen: this.actual === undefined, error, invertedError, }); } /** * Check if the value is `null`. * * @example * ``` * expect(myNullValue).toBeNull(); * ``` * * @returns the assertion instance */ toBeNull() { const error = new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> to be null`, }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected the value NOT to be null", }); return this.execute({ assertWhen: this.actual === null, error, invertedError, }); } /** * Check if the value is present. This means that the value should not be * `undefined`. * * @example * ``` * expect(PI).toBePresent(); * ``` * * @returns the assertion instance */ toBePresent() { const error = new assert_1.AssertionError({ actual: this.actual, message: "Expected the value to be present", }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected the value NOT to be present", }); return this.execute({ assertWhen: this.actual !== undefined, error, invertedError, }); } /** * Check if the value is a truthy value. There are six falsy values in * JavaScript: `null`, `undefined`, `0`, `""`, `false`, `NaN`. Everything * else is truthy. * * @example * ``` * expect("hello world").toBeTruthy(); * expect(128).toBeTruthy(); * ``` * * @returns the assertion instance */ toBeTruthy() { const error = new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> to be a truthy value`, }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> NOT to be a truthy value`, }); return this.execute({ assertWhen: !!this.actual, error, invertedError, }); } /** * Check if the value is a falsy value. There are six falsy values in * JavaScript: `null`, `undefined`, `0`, `""`, `false`, `NaN`. Everything * else is truthy. * * @example * ``` * expect(0).toBeFalsy(); * expect("").toBeFalsy(); * ``` * @returns the assertion instance */ toBeFalsy() { const error = new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> to be a falsy value`, }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> NOT to be a falsy value`, }); return this.execute({ assertWhen: !this.actual, error, invertedError, }); } /** * Check if the value is an instance of the provided constructor. * * @example * ``` * expect(pontiac).toBeInstanceOf(Car); * * expect(today).toBeInstanceOf(Date); * ``` * * @param Expected the constructor the value should be an instance * @returns the assertion instance */ toBeInstanceOf(Expected) { const error = new assert_1.AssertionError({ actual: this.actual, message: `Expected value to be an instance of <${Expected.name}>`, }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: `Expected value NOT to be an instance of <${Expected.name}>`, }); return this.execute({ assertWhen: this.actual instanceof Expected, error, invertedError, }); } /** * Check if the value is deep strict equal to another value. * * @example * ``` * expect(3 + 2).toBeEqual(5); * expect({ a: { b: 1 } }).toBeEqual({ a: { b: 1 } }); * expect(today).toBeEqual(new Date(today.toISOString())); * ``` * * @param expected the value to compare for deep equality * @returns the assertion instance */ toBeEqual(expected) { const error = new assert_1.AssertionError({ actual: this.actual, expected, message: "Expected both values to be deep equal", }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected both values to NOT be deep equal", }); return this.execute({ assertWhen: (0, es6_1.default)(this.actual, expected), error, invertedError, }); } /** * Check if the value is shallow equal to another value. * * @example * ``` * expect(3 + 2).toBeSimilar(5); * expect({ a: 1 }).toBeSimilar({ a: 1 }); * * expect({ a: { b: 1 } }).not.toBeSimilar({ a: {b: 1} }); * ``` * * @param expected the value to compare for shallow equality * @returns the assertion instance */ toBeSimilar(expected) { const error = new assert_1.AssertionError({ actual: this.actual, expected, message: "Expected both values to be similar", }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected both values to NOT be similar", }); const areShallowEqual = () => { if (this.actual instanceof Date && expected instanceof Date) { return this.actual.getTime() === expected.getTime(); } if (((0, guards_1.isStruct)(this.actual) && (0, guards_1.isStruct)(expected)) || (Array.isArray(this.actual) && Array.isArray(expected))) { const actualKeys = Object.keys(this.actual); const expectedKeys = Object.keys(expected); const sizeMatch = actualKeys.length === expectedKeys.length; const valuesMatch = actualKeys.every(key => this.actual[key] === expected[key]); return sizeMatch && valuesMatch; } return Object.is(this.actual, expected); }; const areBothNaN = typeof this.actual === "number" && typeof expected === "number" && isNaN(this.actual) && isNaN(expected); return this.execute({ assertWhen: areShallowEqual() || areBothNaN || this.actual === expected, error, invertedError, }); } /** * Check if the value is the same as another value. * * @example * ``` * const x = { a: 1 }; * const y = x; * * expect(x).toBeSame(x); * expect(x).toBeSame(y); * * expect(x).not.toBeSame({ ...x }); * ``` * * @param expected the value to compare for referential equality * @returns the assertion instance */ toBeSame(expected) { const error = new assert_1.AssertionError({ actual: this.actual, expected, message: "Expected both values to be the same", }); const invertedError = new assert_1.AssertionError({ actual: this.actual, message: "Expected both values to NOT be the same", }); return this.execute({ assertWhen: this.actual === expected, error, invertedError, }); } /** * Alias of `.toBeSame(..)` assertion. * * @example * ``` * const x = { a: 1 }; * const y = x; * * expect(x).toBeSameAs(x); * expect(x).toBeSameAs(y); * * expect(x).not.toBeSameAs({ ...x }); * ``` * * @param expected the value to compare for referential equality * @returns the assertion instance */ toBeSameAs(expected) { return this.toBeSame(expected); } /** * Another alias of `.toBeSame(..)` assertion. * * @example * ``` * const x = { a: 1 }; * const y = x; * * expect(x).toBe(x); * expect(x).toBe(y); * * expect(x).not.toBe({ ...x }); * ``` * * @param expected the value to compare for referential equality * @returns the assertion instance */ toBe(expected) { return this.toBeSame(expected); } /** * Checks if the value is of a specific data type. The supported data types * are the same as the `typeof` operator, plus an additional `array` which * allows desabiguation between `object` (which can also be an array). * * @example * ``` * const arr = [1, 2, 3]; * * expect(arr).toBeOfType("array"); * expect(arr[0]).toBeOfType("number"); * expect(arr[9]).toBeOfType("undefined"); * ``` * * @param expected the expected data type * @returns the assertion instance */ toBeOfType(expected) { const error = new assert_1.AssertionError({ actual: typeof this.actual, expected, message: `Expected <${(0, messages_1.prettify)(this.actual)}> to be of type <${expected}>`, }); const invertedError = new assert_1.AssertionError({ actual: typeof this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> NOT to be of type <${expected}>`, }); return this.execute({ assertWhen: expected === "array" ? Array.isArray(this.actual) : typeof this.actual === expected, error, invertedError, }); } /** * Check first if the value is of some specific type, in which case returns * an assertion instance for that specific type. The new assertion is built * from a factory that should extend from the base {@link Assertion} class. * * We provide some basic factories in `TypeFactories`. If you need some * other factory for a custom assertion for instance, you can easily create * one from a Factory reference and a predicate. * * @example * ``` * expect(unknownValue) * .asType(TypeFactories.STRING) * .toStartWith("/api/"); * * expect(uuid) * .asType({ * Factory: UUIDAssertion, // a custom UUID assertion * predicate: (value): value is UUID => { * return typeof value === "string" && UUID.PATTER.test(value); * }, * }) * .isValid(); * ``` * * @typeParam S the type of the factory's value * @typeParam A the type of the assertion factory * @param typeFactory a factory to assert the type and create an assertion * @returns a more specific assertion based on the factory type */ asType(typeFactory) { const { Factory, predicate, typeName } = typeFactory; if (this.inverted) { throw new UnsupportedOperationError_1.UnsupportedOperationError("The `.not` modifier is not allowed on `.asType(..)` method"); } if (predicate(this.actual)) { return new Factory(this.actual); } throw new assert_1.AssertionError({ actual: this.actual, message: `Expected <${(0, messages_1.prettify)(this.actual)}> to be of type "${typeName}"`, }); } proxyInverter(isInverted) { return (target, p) => { const key = (0, guards_1.isKeyOf)(target, p) ? p : undefined; if (key === "inverted") { return isInverted; } return key ? target[key] : undefined; }; } } exports.Assertion = Assertion; //# sourceMappingURL=Assertion.js.map