import * as plugins from './smartexpect.plugins.js'; export type TExecutionType = 'sync' | 'async'; export class Assertion { executionMode: TExecutionType; baseReference: any; propertyDrillDown: string[] = []; private notSetting = false; private timeoutSetting = 0; constructor(baseReferenceArg: any, executionModeArg: TExecutionType) { this.baseReference = baseReferenceArg; this.executionMode = executionModeArg; } private getObjectToTestReference() { let returnObjectToTestReference = this.baseReference; for (const property of this.propertyDrillDown) { returnObjectToTestReference = returnObjectToTestReference[property]; } return returnObjectToTestReference; } public get not() { this.notSetting = true; return this; } public timeout(millisArg: number) { this.timeoutSetting = millisArg; return this; } private failMessage: string; public setFailMessage(failMessageArg: string) { this.failMessage = failMessageArg; return this; } private successMessage: string; public setSuccessMessage(successMessageArg: string) { this.successMessage = successMessageArg; return this; } private runCheck(checkFunction: () => any) { const runDirectOrNegated = (checkFunction: () => any) => { if (!this.notSetting) { return checkFunction(); } else { let isOk = false; try { runDirectOrNegated(checkFunction()); } catch (e) { isOk = true; } if (!isOk) { throw new Error(this.failMessage || 'Negated assertion is not ok!'); } } }; if (this.executionMode === 'async') { const done = plugins.smartpromise.defer(); if (!(this.baseReference instanceof Promise)) { done.reject(new Error(`${this.baseReference} is not of type promise.`)); } else { if (this.timeoutSetting) { plugins.smartdelay.delayFor(this.timeoutSetting).then(() => { if (done.status === 'pending') { done.reject(new Error(`${this.baseReference} timed out at ${this.timeoutSetting}!`)); } }); } this.baseReference.then((promiseResultArg) => { this.baseReference = promiseResultArg; done.resolve(runDirectOrNegated(checkFunction)); }); } return done.promise; } else { return runDirectOrNegated(checkFunction); } } /** * checks if the given object is defined */ public toBeDefined() { return this.runCheck(() => { if (this.getObjectToTestReference() === undefined) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not defined` ); } }); } /** * checks if the given object is not defined */ public toBeTypeofString() { return this.runCheck(() => { if (typeof this.getObjectToTestReference() !== 'string') { throw new Error( this.failMessage || `Assertion failed: ${this.baseReference} with drill down ${ this.propertyDrillDown } is not of type string, but typeof ${typeof this.baseReference}` ); } }); } public toBeTypeofNumber() { return this.runCheck(() => { if (typeof this.getObjectToTestReference() !== 'number') { throw new Error( this.failMessage || `Assertion failed: ${this.baseReference} with drill down ${ this.propertyDrillDown } is not of type string, but typeof ${typeof this.baseReference}` ); } }); } public toBeTypeofBoolean() { return this.runCheck(() => { if (typeof this.getObjectToTestReference() !== 'boolean') { throw new Error( this.failMessage || `Assertion failed: ${this.baseReference} with drill down ${ this.propertyDrillDown } is not of type string, but typeof ${typeof this.baseReference}` ); } }); } public toEqual(comparisonObject: any) { return this.runCheck(() => { const result = plugins.fastDeepEqual(this.getObjectToTestReference(), comparisonObject); if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not equal ${comparisonObject}` ); } }); } public toMatch(comparisonObject: RegExp) { return this.runCheck(() => { const result = comparisonObject.test(this.getObjectToTestReference()); if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not equal ${comparisonObject}` ); } }); } public toBeTrue() { return this.runCheck(() => { const result = typeof this.getObjectToTestReference() === 'boolean' && this.getObjectToTestReference() === true; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not true or not of type boolean` ); } }); } public toBeFalse() { return this.runCheck(() => { const result = typeof this.getObjectToTestReference() === 'boolean' && this.getObjectToTestReference() === false; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not false or not of type boolean` ); } }); } public toBeInstanceOf(classArg: any) { return this.runCheck(() => { const result = this.getObjectToTestReference() instanceof classArg; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not an instance of ${classArg}` ); } }); } public toHaveProperty(propertyArg: string, equalsArg?: any) { return this.runCheck(() => { const result = !!this.getObjectToTestReference()[propertyArg]; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have property ${propertyArg}` ); } if (equalsArg) { if (result !== equalsArg) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does have property ${propertyArg}, but it does not equal ${equalsArg}` ); } } }); } public toHaveDeepProperty(properties: string[]) { return this.runCheck(() => { let obj = this.getObjectToTestReference(); let currentPath = ''; for (const property of properties) { if (currentPath) { currentPath += `.${property}`; } else { currentPath = property; } if (!obj || !(property in obj)) { throw new Error(this.failMessage || `Missing property at path "${currentPath}" in ${this.baseReference}`); } obj = obj[property]; } }); } public toBeGreaterThan(numberArg: number) { return this.runCheck(() => { const result = this.getObjectToTestReference() > numberArg; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not greater than ${numberArg}` ); } }); } public toBeLessThan(numberArg: number) { return this.runCheck(() => { const result = this.getObjectToTestReference() < numberArg; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not less than ${numberArg}` ); } }); } public toBeNull() { return this.runCheck(() => { const result = this.getObjectToTestReference() === null; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not null` ); } }); } public toBeUndefined() { return this.runCheck(() => { const result = this.getObjectToTestReference() === undefined; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not undefined` ); } }); } public toBeNullOrUndefined() { return this.runCheck(() => { const result = this.getObjectToTestReference() === null || this.getObjectToTestReference() === undefined; if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not null or undefined` ); } }); } // Array public toContain(itemArg: any) { return this.runCheck(() => { const result = this.getObjectToTestReference() instanceof Array && this.getObjectToTestReference().includes(itemArg); if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not contain ${itemArg}` ); } }); } public toBeEmptyArray() { return this.runCheck(() => { const arrayRef = this.getObjectToTestReference(); if (!Array.isArray(arrayRef) || arrayRef.length !== 0) { throw new Error(this.failMessage || `Expected ${this.baseReference} to be an empty array, but it was not.`); } }); } public toContainAll(values: any[]) { return this.runCheck(() => { const arrayRef = this.getObjectToTestReference(); if (!Array.isArray(arrayRef)) { throw new Error(this.failMessage || `Expected ${this.baseReference} to be an array.`); } for (const value of values) { if (!arrayRef.includes(value)) { throw new Error( this.failMessage || `Expected ${this.baseReference} to include value "${value}", but it did not.` ); } } }); } public toExclude(value: any) { return this.runCheck(() => { const arrayRef = this.getObjectToTestReference(); if (!Array.isArray(arrayRef)) { throw new Error(this.failMessage || `Expected ${this.baseReference} to be an array.`); } if (arrayRef.includes(value)) { throw new Error( this.failMessage || `Expected ${this.baseReference} to exclude value "${value}", but it included it.` ); } }); } public toStartWith(itemArg: any) { return this.runCheck(() => { const testObject = this.getObjectToTestReference(); const result = typeof testObject === 'string' && testObject.startsWith(itemArg); if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not contain ${itemArg}` ); } }); } public toEndWith(itemArg: any) { return this.runCheck(() => { const testObject = this.getObjectToTestReference(); const result = typeof testObject === 'string' && testObject.endsWith(itemArg); if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not contain ${itemArg}` ); } }); } // ... previous code ... public toBeOneOf(values: any[]) { return this.runCheck(() => { const result = values.includes(this.getObjectToTestReference()); if (!result) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not one of ${values}` ); } }); } public toHaveLength(length: number) { return this.runCheck(() => { const obj = this.getObjectToTestReference(); if (typeof obj.length !== 'number' || obj.length !== length) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have a length of ${length}` ); } }); } public toBeCloseTo(value: number, precision = 2) { return this.runCheck(() => { const difference = Math.abs(this.getObjectToTestReference() - value); if (difference > Math.pow(10, -precision) / 2) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not close to ${value} up to ${precision} decimal places` ); } }); } public toThrow(expectedError?: any) { return this.runCheck(() => { let thrown = false; try { this.getObjectToTestReference()(); } catch (e) { thrown = true; if (expectedError && !(e instanceof expectedError)) { throw new Error( this.failMessage || `Expected function to throw ${expectedError.name}, but it threw ${e.name}` ); } } if (!thrown) { throw new Error(`Expected function to throw, but it didn't.`); } }); } public toBeTruthy() { return this.runCheck(() => { if (!this.getObjectToTestReference()) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not truthy` ); } }); } public toBeFalsy() { return this.runCheck(() => { if (this.getObjectToTestReference()) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not falsy` ); } }); } public toBeGreaterThanOrEqual(numberArg: number) { return this.runCheck(() => { if (this.getObjectToTestReference() <= numberArg) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not greater than or equal to ${numberArg}` ); } }); } public toBeLessThanOrEqual(numberArg: number) { return this.runCheck(() => { if (this.getObjectToTestReference() >= numberArg) { throw new Error( `${this.baseReference} with drill down ${this.propertyDrillDown} is not less than or equal to ${numberArg}` ); } }); } public toMatchObject(objectArg: object) { return this.runCheck(() => { const partialMatch = plugins.fastDeepEqual(this.getObjectToTestReference(), objectArg); // Note: Implement a deep comparison function or use one from a library if (!partialMatch) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not match the object ${objectArg}` ); } }); } public toContainEqual(value: any) { return this.runCheck(() => { const arr = this.getObjectToTestReference(); const found = arr.some((item: any) => plugins.fastDeepEqual(item, value)); // Assuming fastDeepEqual checks deep equality if (!found) { throw new Error( `${this.baseReference} with drill down ${this.propertyDrillDown} does not contain the value ${value}` ); } }); } public toBeArray() { return this.runCheck(() => { if (!Array.isArray(this.getObjectToTestReference())) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not an array` ); } }); } public toInclude(substring: string) { return this.runCheck(() => { if (!this.getObjectToTestReference().includes(substring)) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not include the substring ${substring}` ); } }); } public toHaveLengthGreaterThan(length: number) { return this.runCheck(() => { const obj = this.getObjectToTestReference(); if (typeof obj.length !== 'number' || obj.length <= length) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have a length greater than ${length}` ); } }); } public toHaveLengthLessThan(length: number) { return this.runCheck(() => { const obj = this.getObjectToTestReference(); if (typeof obj.length !== 'number' || obj.length >= length) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} does not have a length less than ${length}` ); } }); } public toBeDate() { return this.runCheck(() => { if (!(this.getObjectToTestReference() instanceof Date)) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not a date` ); } }); } public toBeBeforeDate(date: Date) { return this.runCheck(() => { if (!(this.getObjectToTestReference() < date)) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not before ${date}` ); } }); } public toBeAfterDate(date: Date) { return this.runCheck(() => { if (!(this.getObjectToTestReference() > date)) { throw new Error( this.failMessage || `${this.baseReference} with drill down ${this.propertyDrillDown} is not after ${date}` ); } }); } public customAssertion(assertionFunction: (value: any) => boolean, errorMessage: string) { return this.runCheck(() => { if (!assertionFunction(this.getObjectToTestReference())) { throw new Error(this.failMessage || errorMessage); } }); } public property(propertyNameArg: string) { this.propertyDrillDown.push(propertyNameArg); return this; } }