From 2ac753ed2d588756590c1929f13c770c8cc8d038 Mon Sep 17 00:00:00 2001 From: JoseLion Date: Fri, 26 Aug 2022 00:05:10 -0500 Subject: [PATCH 1/4] feat(assertion): Add `.toBeInstanceOf(Constructor)` matcher --- src/lib/Assertion.ts | 30 ++++++++++++++++++++++++ src/lib/ObjectAssertion.ts | 2 +- test/lib/Assertion.test.ts | 47 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/lib/Assertion.ts b/src/lib/Assertion.ts index 7a30cac..c49460d 100644 --- a/src/lib/Assertion.ts +++ b/src/lib/Assertion.ts @@ -217,6 +217,36 @@ export class Assertion { }); } + /** + * Check if the value is an instance of the 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: new (...args: any[]) => any): 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/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..88d3d01 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 vlaue 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", () => { [ From ad464513c81bc23d67098e472817f242431f07c0 Mon Sep 17 00:00:00 2001 From: JoseLion Date: Fri, 26 Aug 2022 17:39:17 -0500 Subject: [PATCH 2/4] Fix typos --- src/lib/Assertion.ts | 2 +- test/lib/Assertion.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Assertion.ts b/src/lib/Assertion.ts index c49460d..0dc1b5b 100644 --- a/src/lib/Assertion.ts +++ b/src/lib/Assertion.ts @@ -218,7 +218,7 @@ export class Assertion { } /** - * Check if the value is an instance of the the provided constructor. + * Check if the value is an instance of the provided constructor. * * @example * ``` diff --git a/test/lib/Assertion.test.ts b/test/lib/Assertion.test.ts index 88d3d01..55fb43a 100644 --- a/test/lib/Assertion.test.ts +++ b/test/lib/Assertion.test.ts @@ -297,7 +297,7 @@ describe("[Unit] Assertion.test.ts", () => { }); }); - context("when the vlaue in not an instance of the constructor", () => { + context("when the value in not an instance of the constructor", () => { it("throws an assertion error", () => { const test = new Assertion(new Date()); From dd1134526ffe81bb3940cb93481662e1524d5170 Mon Sep 17 00:00:00 2001 From: JoseLion Date: Fri, 26 Aug 2022 18:01:26 -0500 Subject: [PATCH 3/4] Change `Class` to `Constructor` --- src/lib/Assertion.ts | 6 +++++- src/lib/FunctionAssertion.ts | 12 ++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/Assertion.ts b/src/lib/Assertion.ts index 0dc1b5b..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 @@ -230,7 +234,7 @@ export class Assertion { * @param Expected the constructor the value should be an instance * @returns the assertion instance */ - public toBeInstanceOf(Expected: new (...args: any[]) => any): this { + public toBeInstanceOf(Expected: Constructor): this { const error = new AssertionError({ actual: this.actual, message: `Expected value to be an instance of <${Expected.name}>` 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}>` From 8bfe7f65804eccd7e207ff9e95e55cb19c4fbfd0 Mon Sep 17 00:00:00 2001 From: JoseLion Date: Fri, 26 Aug 2022 18:12:24 -0500 Subject: [PATCH 4/4] Add Coming Soon to the readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) 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)):