diff --git a/README.md b/README.md index 3b316e7..a6a2439 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,10 @@ For a list of all matchers and extended documentation, please refer to the [API You can find the full API reference [here](https://stackbuilders.github.io/assertive-ts/docs/build/) +## Coming Soon + +- Extension mechanism ⚙️ + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): diff --git a/src/lib/Assertion.ts b/src/lib/Assertion.ts index 7a30cac..db1de5a 100644 --- a/src/lib/Assertion.ts +++ b/src/lib/Assertion.ts @@ -5,6 +5,10 @@ import { UnsupportedOperationError } from "./errors/UnsupportedOperationError"; import { isJSObject, isKeyOf } from "./helpers/guards"; import { TypeFactory } from "./helpers/TypeFactories"; +export interface Constructor extends Function { + prototype: T; +} + export interface ExecuteOptions { /** * The condition for when the assertion should pass. The negation of this @@ -217,6 +221,36 @@ export class Assertion { }); } + /** + * 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 + */ + public toBeInstanceOf(Expected: Constructor): this { + const error = new AssertionError({ + actual: this.actual, + message: `Expected value to be an instance of <${Expected.name}>` + }); + const invertedError = new 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 equal to another value. * diff --git a/src/lib/FunctionAssertion.ts b/src/lib/FunctionAssertion.ts index ce0f6f9..9a441a3 100644 --- a/src/lib/FunctionAssertion.ts +++ b/src/lib/FunctionAssertion.ts @@ -1,15 +1,11 @@ import { AssertionError } from "assert"; -import { Assertion } from "./Assertion"; +import { Assertion, Constructor } from "./Assertion"; import { ErrorAssertion } from "./ErrorAssertion"; import { TypeFactory } from "./helpers/TypeFactories"; export type AnyFunction = (...args: any[]) => any; -export interface Class extends Function { - prototype: T; -} - const NoThrow = Symbol("NoThrow"); /** @@ -76,8 +72,8 @@ export class FunctionAssertion extends Assertion { * @returns a new {@link ErrorAssertion} to assert over the error */ public toThrowError(): ErrorAssertion; - public toThrowError(ExpectedType: Class): ErrorAssertion; - public toThrowError(ExpectedType?: Class): ErrorAssertion { + public toThrowError(Expected: Constructor): ErrorAssertion; + public toThrowError(Expected?: Constructor): ErrorAssertion { const captured = this.captureError(); if (captured === NoThrow) { @@ -87,7 +83,7 @@ export class FunctionAssertion extends Assertion { }); } - const ErrorType = ExpectedType ?? Error; + const ErrorType = Expected ?? Error; const error = new AssertionError({ actual: captured, message: `Expected the function to throw an error instance of <${ErrorType.name}>` diff --git a/src/lib/ObjectAssertion.ts b/src/lib/ObjectAssertion.ts index 0a4eceb..a5efc47 100644 --- a/src/lib/ObjectAssertion.ts +++ b/src/lib/ObjectAssertion.ts @@ -3,7 +3,7 @@ import { isDeepStrictEqual } from "util"; import { Assertion } from "./Assertion"; -export type JSObject = Record; +export type JSObject = Record; export type Entry = K extends keyof T ? [K, T[K]] diff --git a/test/lib/Assertion.test.ts b/test/lib/Assertion.test.ts index 3713cc6..55fb43a 100644 --- a/test/lib/Assertion.test.ts +++ b/test/lib/Assertion.test.ts @@ -52,6 +52,19 @@ const BASE_DIFFS = [ ["date", TODAY, new Date("2021-12-10T00:00:00.001Z")] ]; +class Car { + + private readonly model: string; + + constructor(model: string) { + this.model = model; + } + + public showModel(): string { + return this.model; + } +} + function truthyAsText(value: typeof TRUTHY_VALUES[number]): string { if (Array.isArray(value) && value.length === 0) { return "Empty array"; @@ -263,6 +276,40 @@ describe("[Unit] Assertion.test.ts", () => { }); }); + describe(".toBeInstanceOf", () => { + context("when the value is an instance of the constructor", () => { + const variants = [ + [new Date(), Date], + [new Error("failed!"), Error], + [new Car("Pontiac GT-37"), Car] + ] as const; + + variants.forEach(([value, Constructor]) => { + it(`[Instance: ${Constructor.name}]: returns the assertion instance`, () => { + const test = new Assertion(value); + + assert.deepStrictEqual(test.toBeInstanceOf(Constructor), test); + assert.throws(() => test.not.toBeInstanceOf(Constructor), { + message: `Expected value NOT to be an instance of <${Constructor.name}>`, + name: AssertionError.name + }); + }); + }); + }); + + context("when the value in not an instance of the constructor", () => { + it("throws an assertion error", () => { + const test = new Assertion(new Date()); + + assert.throws(() => test.toBeInstanceOf(Car), { + message: "Expected value to be an instance of ", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toBeInstanceOf(Car), test); + }); + }); + }); + describe(".toBeEqual", () => { context("when the value is referentially, shallow, and deep equal", () => { [