import { Assertion, AnyMatcher, AnythingMatcher } from '../smartexpect.classes.assertion.js'; import type { TExecutionType } from '../types.js'; import * as plugins from '../plugins.js'; /** * Namespace for object-specific matchers */ export class ObjectMatchers { constructor(private assertion: Assertion) {} toEqual(expected: any) { return this.assertion.customAssertion( (v) => plugins.fastDeepEqual(v, expected), (v) => `Expected objects to be deeply equal to ${JSON.stringify(expected, null, 2)}` + `\nReceived: ${JSON.stringify(v, null, 2)}` ); } toMatchObject(expected: object) { return this.assertion.customAssertion( (v) => { const obj = v as any; for (const key of Object.keys(expected)) { const expectedVal = (expected as any)[key]; const actualVal = obj[key]; if (expectedVal instanceof AnyMatcher) { const ctor = expectedVal.expectedConstructor; if (ctor === Number) { if (typeof actualVal !== 'number') return false; } else if (ctor === String) { if (typeof actualVal !== 'string') return false; } else if (ctor === Boolean) { if (typeof actualVal !== 'boolean') return false; } else { if (!(actualVal instanceof ctor)) return false; } } else if (expectedVal instanceof AnythingMatcher) { if (actualVal === null || actualVal === undefined) { return false; } } else if (!plugins.fastDeepEqual(actualVal, expectedVal)) { return false; } } return true; }, (v) => `Expected object to match properties ${JSON.stringify(expected, null, 2)}` + `\nReceived: ${JSON.stringify(v, null, 2)}` ); } toBeInstanceOf(constructor: any) { return this.assertion.customAssertion( (v) => (v as any) instanceof constructor, `Expected object to be instance of ${constructor.name || constructor}` ); } toHaveProperty(property: string, value?: any) { return this.assertion.customAssertion( (v) => { const obj = v as any; if (!(property in obj)) { return false; } if (arguments.length === 2) { return plugins.fastDeepEqual(obj[property], value); } return true; }, `Expected object to have property ${property}${value !== undefined ? ` with value ${JSON.stringify(value)}` : ''}` ); } toHaveDeepProperty(path: string[]) { return this.assertion.customAssertion( (v) => { let obj: any = v; for (const key of path) { if (obj == null || !(key in obj)) { return false; } obj = obj[key]; } return true; }, `Expected object to have deep property path ${JSON.stringify(path)}` ); } toBeNull() { return this.assertion.customAssertion( (v) => v === null, `Expected value to be null` ); } toBeUndefined() { return this.assertion.customAssertion( (v) => v === undefined, `Expected value to be undefined` ); } toBeNullOrUndefined() { return this.assertion.customAssertion( (v) => v === null || v === undefined, `Expected value to be null or undefined` ); } /** * Checks own property only (not inherited) */ toHaveOwnProperty(property: string, value?: any) { return this.assertion.customAssertion( (v) => { const obj = v as any; if (!Object.prototype.hasOwnProperty.call(obj, property)) { return false; } if (arguments.length === 2) { return plugins.fastDeepEqual(obj[property], value); } return true; }, (v) => `Expected object to have own property ${property}` + (value !== undefined ? ` with value ${JSON.stringify(value)}` : ``) + `\nReceived: ${JSON.stringify(v, null, 2)}` ); } }