@@ -29,15 +29,25 @@ const {
29
29
Error,
30
30
ErrorCaptureStackTrace,
31
31
FunctionPrototypeBind,
32
+ FunctionPrototypeCall,
33
+ MapPrototypeGet,
34
+ MapPrototypeHas,
32
35
NumberIsNaN,
33
36
ObjectAssign,
37
+ ObjectGetPrototypeOf,
34
38
ObjectIs,
35
39
ObjectKeys,
40
+ ObjectPrototype,
36
41
ObjectPrototypeIsPrototypeOf,
42
+ ObjectPrototypeToString,
37
43
ReflectApply,
44
+ ReflectHas,
45
+ ReflectOwnKeys,
38
46
RegExpPrototypeExec,
39
47
RegExpPrototypeSymbolReplace,
40
48
SafeMap,
49
+ SafeSet,
50
+ SetPrototypeHas,
41
51
String,
42
52
StringPrototypeCharCodeAt,
43
53
StringPrototypeIncludes,
@@ -46,6 +56,7 @@ const {
46
56
StringPrototypeSlice,
47
57
StringPrototypeSplit,
48
58
StringPrototypeStartsWith,
59
+ SymbolIterator,
49
60
} = primordials ;
50
61
51
62
const { Buffer } = require ( 'buffer' ) ;
@@ -63,7 +74,7 @@ const {
63
74
const AssertionError = require ( 'internal/assert/assertion_error' ) ;
64
75
const { openSync, closeSync, readSync } = require ( 'fs' ) ;
65
76
const { inspect } = require ( 'internal/util/inspect' ) ;
66
- const { isPromise, isRegExp } = require ( 'internal/util/types' ) ;
77
+ const { isPromise, isRegExp, isMap , isSet } = require ( 'internal/util/types' ) ;
67
78
const { EOL } = require ( 'internal/constants' ) ;
68
79
const { BuiltinModule } = require ( 'internal/bootstrap/realm' ) ;
69
80
const { isError, deprecate } = require ( 'internal/util' ) ;
@@ -608,6 +619,139 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
608
619
}
609
620
} ;
610
621
622
+ /**
623
+ * Compares two objects or values recursively to check if they are equal.
624
+ * @param {any } actual - The actual value to compare.
625
+ * @param {any } expected - The expected value to compare.
626
+ * @param {boolean } [loose=false] - Whether to use loose comparison (==) or strict comparison (===). Defaults to false.
627
+ * @param {Set } [comparedObjects=new Set()] - Set to track compared objects for handling circular references.
628
+ * @returns {boolean } - Returns `true` if the actual value matches the expected value, otherwise `false`.
629
+ * @example
630
+ * // Loose comparison (default)
631
+ * compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: '2'}); // true
632
+ *
633
+ * // Strict comparison
634
+ * compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}, true); // true
635
+ */
636
+ function compareBranch (
637
+ actual ,
638
+ expected ,
639
+ loose = false ,
640
+ comparedObjects = new SafeSet ( ) ,
641
+ ) {
642
+ function isPlainObject ( obj ) {
643
+ if ( typeof obj !== 'object' || obj === null ) return false ;
644
+ const proto = ObjectGetPrototypeOf ( obj ) ;
645
+ return proto === ObjectPrototype || proto === null || ObjectPrototypeToString ( obj ) === '[object Object]' ;
646
+ }
647
+
648
+ // Check for Map object equality
649
+ if ( isMap ( actual ) && isMap ( expected ) ) {
650
+ if ( actual . size !== expected . size ) return false ;
651
+ const safeIterator = FunctionPrototypeCall ( SafeMap . prototype [ SymbolIterator ] , actual ) ;
652
+ for ( const { 0 : key , 1 : val } of safeIterator ) {
653
+ if ( ! MapPrototypeHas ( expected , key ) ) return false ;
654
+ if ( ! compareBranch ( val , MapPrototypeGet ( expected , key ) , loose , comparedObjects ) )
655
+ return false ;
656
+ }
657
+ return true ;
658
+ }
659
+
660
+ // Check for Set object equality
661
+ if ( isSet ( actual ) && isSet ( expected ) ) {
662
+ if ( actual . size !== expected . size ) return false ;
663
+ const safeIterator = FunctionPrototypeCall ( SafeSet . prototype [ SymbolIterator ] , actual ) ;
664
+ for ( const item of safeIterator ) {
665
+ if ( ! SetPrototypeHas ( expected , item ) ) return false ;
666
+ }
667
+ return true ;
668
+ }
669
+
670
+ // Check non object types equality
671
+ if ( ! isPlainObject ( actual ) || ! isPlainObject ( expected ) ) {
672
+ if ( isDeepEqual === undefined ) lazyLoadComparison ( ) ;
673
+ return loose ? isDeepEqual ( actual , expected ) : isDeepStrictEqual ( actual , expected ) ;
674
+ }
675
+
676
+ // Check if actual and expected are null or not objects
677
+ if ( actual == null || expected == null ) {
678
+ return false ;
679
+ }
680
+
681
+ // Use Reflect.ownKeys() instead of Object.keys() to include symbol properties
682
+ const keysExpected = ReflectOwnKeys ( expected ) ;
683
+
684
+ // Handle circular references
685
+ if ( comparedObjects . has ( actual ) ) {
686
+ return true ;
687
+ }
688
+ comparedObjects . add ( actual ) ;
689
+
690
+ // Check if all expected keys and values match
691
+ for ( let i = 0 ; i < keysExpected . length ; i ++ ) {
692
+ const key = keysExpected [ i ] ;
693
+ assert (
694
+ ReflectHas ( actual , key ) ,
695
+ new AssertionError ( { message : `Expected key ${ String ( key ) } not found in actual object` } ) ,
696
+ ) ;
697
+ if ( ! compareBranch ( actual [ key ] , expected [ key ] , loose , comparedObjects ) ) {
698
+ return false ;
699
+ }
700
+ }
701
+
702
+ return true ;
703
+ }
704
+
705
+ /**
706
+ * The strict equivalence assertion test between two objects
707
+ * @param {any } actual
708
+ * @param {any } expected
709
+ * @param {string | Error } [message]
710
+ * @returns {void }
711
+ */
712
+ assert . matchObjectStrict = function matchObjectStrict (
713
+ actual ,
714
+ expected ,
715
+ message ,
716
+ ) {
717
+ if ( arguments . length < 2 ) {
718
+ throw new ERR_MISSING_ARGS ( 'actual' , 'expected' ) ;
719
+ }
720
+
721
+ if ( ! compareBranch ( actual , expected ) ) {
722
+ innerFail ( {
723
+ actual,
724
+ expected,
725
+ message,
726
+ operator : 'matchObjectStrict' ,
727
+ stackStartFn : matchObjectStrict ,
728
+ } ) ;
729
+ }
730
+ } ;
731
+
732
+ /**
733
+ * The equivalence assertion test between two objects
734
+ * @param {any } actual
735
+ * @param {any } expected
736
+ * @param {string | Error } [message]
737
+ * @returns {void }
738
+ */
739
+ assert . matchObject = function matchObject ( actual , expected , message ) {
740
+ if ( arguments . length < 2 ) {
741
+ throw new ERR_MISSING_ARGS ( 'actual' , 'expected' ) ;
742
+ }
743
+
744
+ if ( ! compareBranch ( actual , expected , true ) ) {
745
+ innerFail ( {
746
+ actual,
747
+ expected,
748
+ message,
749
+ operator : 'matchObject' ,
750
+ stackStartFn : matchObject ,
751
+ } ) ;
752
+ }
753
+ } ;
754
+
611
755
class Comparison {
612
756
constructor ( obj , keys , actual ) {
613
757
for ( const key of keys ) {
0 commit comments