2025-04-28 20:42:58 +00:00
|
|
|
import { Assertion, AnyMatcher, AnythingMatcher } from '../smartexpect.classes.assertion.js';
|
2025-04-28 19:58:32 +00:00
|
|
|
import * as plugins from '../plugins.js';
|
2025-04-28 19:10:27 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Namespace for object-specific matchers
|
|
|
|
*/
|
|
|
|
export class ObjectMatchers<T extends object> {
|
|
|
|
constructor(private assertion: Assertion<T>) {}
|
|
|
|
|
|
|
|
toEqual(expected: any) {
|
2025-04-28 19:58:32 +00:00
|
|
|
return this.assertion.customAssertion(
|
|
|
|
(v) => plugins.fastDeepEqual(v, expected),
|
2025-04-28 20:42:58 +00:00
|
|
|
(v) =>
|
|
|
|
`Expected objects to be deeply equal to ${JSON.stringify(expected, null, 2)}` +
|
|
|
|
`\nReceived: ${JSON.stringify(v, null, 2)}`
|
2025-04-28 19:58:32 +00:00
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toMatchObject(expected: object) {
|
2025-04-28 19:58:32 +00:00
|
|
|
return this.assertion.customAssertion(
|
|
|
|
(v) => {
|
2025-04-28 20:42:58 +00:00
|
|
|
const obj = v as any;
|
2025-04-28 19:58:32 +00:00
|
|
|
for (const key of Object.keys(expected)) {
|
2025-04-28 20:42:58 +00:00
|
|
|
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)) {
|
2025-04-28 19:58:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
2025-04-28 20:42:58 +00:00
|
|
|
(v) =>
|
|
|
|
`Expected object to match properties ${JSON.stringify(expected, null, 2)}` +
|
|
|
|
`\nReceived: ${JSON.stringify(v, null, 2)}`
|
2025-04-28 19:58:32 +00:00
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toBeInstanceOf(constructor: any) {
|
2025-04-28 19:58:32 +00:00
|
|
|
return this.assertion.customAssertion(
|
|
|
|
(v) => (v as any) instanceof constructor,
|
|
|
|
`Expected object to be instance of ${constructor.name || constructor}`
|
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toHaveProperty(property: string, value?: any) {
|
2025-04-28 19:58:32 +00:00
|
|
|
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)}` : ''}`
|
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toHaveDeepProperty(path: string[]) {
|
2025-04-28 19:58:32 +00:00
|
|
|
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)}`
|
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
toBeNull() {
|
2025-04-28 19:58:32 +00:00
|
|
|
return this.assertion.customAssertion(
|
|
|
|
(v) => v === null,
|
|
|
|
`Expected value to be null`
|
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toBeUndefined() {
|
2025-04-28 19:58:32 +00:00
|
|
|
return this.assertion.customAssertion(
|
|
|
|
(v) => v === undefined,
|
|
|
|
`Expected value to be undefined`
|
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
toBeNullOrUndefined() {
|
2025-04-28 19:58:32 +00:00
|
|
|
return this.assertion.customAssertion(
|
|
|
|
(v) => v === null || v === undefined,
|
|
|
|
`Expected value to be null or undefined`
|
|
|
|
);
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|
2025-04-28 20:42:58 +00:00
|
|
|
/**
|
|
|
|
* 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)}`
|
|
|
|
);
|
|
|
|
}
|
2025-04-28 19:10:27 +00:00
|
|
|
}
|