Hone logo
Hone
Problems

Implement Jest's toEqual Matcher in TypeScript

Jest's toEqual matcher is a powerful tool for deeply comparing objects and arrays. It recursively checks all properties of an object or elements of an array, ensuring that the structure and values are identical. This challenge asks you to reimplement a simplified version of this core Jest functionality in TypeScript. Mastering this will give you a deeper understanding of how assertion libraries work and improve your debugging skills.

Problem Description

You need to create a function, let's call it customToEqual, that mimics the behavior of Jest's toEqual matcher. This function will take two arguments: the actual value and the expected value. It should return true if the two values are deeply equal and false otherwise.

Key Requirements:

  • Primitive Types: Handle comparison of primitive types (strings, numbers, booleans, null, undefined, symbols, bigints) correctly.
  • Object Comparison: For objects, recursively compare their own enumerable properties. The order of properties should not matter.
  • Array Comparison: For arrays, compare elements at each index. The arrays must have the same length and all elements must be deeply equal.
  • Circular References: Implement basic handling for circular references to prevent infinite recursion.
  • Special Object Types: Consider how to handle Date and RegExp objects. They should be compared by their value/pattern, not by reference.

Expected Behavior:

  • customToEqual(5, 5) should return true.
  • customToEqual({ a: 1, b: { c: 2 } }, { b: { c: 2 }, a: 1 }) should return true.
  • customToEqual([1, 2, [3, 4]], [1, 2, [3, 4]]) should return true.
  • customToEqual({ a: 1 }, { a: 2 }) should return false.
  • customToEqual([1, 2], [1, 2, 3]) should return false.
  • customToEqual(new Date('2023-10-27'), new Date('2023-10-27')) should return true.
  • customToEqual(/abc/, /abc/) should return true.

Edge Cases to Consider:

  • Comparing different types (e.g., customToEqual(5, '5') should be false).
  • Comparing null and undefined.
  • Comparing objects with null or undefined properties.
  • Comparing empty objects and arrays.
  • Handling cases where one argument is an object and the other is a primitive.
  • Handling potential circular references within objects/arrays.

Examples

Example 1:

Input:
actual = { name: "Alice", age: 30 }
expected = { age: 30, name: "Alice" }

Output:
true

Explanation: The objects have the same properties and values, regardless of the order.

Example 2:

Input:
actual = [1, { a: 2, b: [3] }]
expected = [1, { b: [3], a: 2 }]

Output:
true

Explanation: The arrays have the same length, and their elements are deeply equal, including nested objects and arrays.

Example 3:

Input:
actual = { a: 1, b: undefined }
expected = { a: 1, b: null }

Output:
false

Explanation: `undefined` and `null` are distinct values.

Example 4 (Circular Reference):

Input:
const obj1: any = { prop: 'value' };
obj1.self = obj1;

const obj2: any = { prop: 'value' };
obj2.self = obj2;

actual = obj1
expected = obj2

Output:
true

Explanation: The objects are structurally identical, including their circular references.

Constraints

  • The comparison should be purely structural and value-based. Do not rely on object identity unless comparing primitives or the exact same object instance.
  • Your customToEqual function should be implemented in TypeScript.
  • The implementation should aim for reasonable performance, avoiding unnecessary deep cloning or excessive recursion where possible. However, correctness is prioritized over micro-optimizations.
  • You can assume that Date and RegExp objects will be instances of their respective classes.

Notes

  • Think about how you will track visited objects to detect and handle circular references. A Map or Set could be useful here.
  • When comparing objects, remember to only consider their own enumerable properties. Object.keys() or Object.getOwnPropertyNames() and Object.getOwnPropertySymbols() might be helpful.
  • Consider the order of checks: it's generally more efficient to check for primitive equality first before diving into object/array comparisons.
  • You can leverage typeof and instanceof operators for type checking.
  • Consider using Object.is() for primitive comparisons as it handles NaN and -0 correctly, which are nuances === doesn't always capture perfectly in this context.
Loading editor...
typescript