2121'use strict' ;
2222
2323const {
24+ ArrayBufferIsView,
25+ ArrayFrom,
26+ ArrayIsArray,
2427 ArrayPrototypeIndexOf,
2528 ArrayPrototypeJoin,
2629 ArrayPrototypePush,
2730 ArrayPrototypeSlice,
2831 Error,
32+ FunctionPrototypeCall,
33+ MapPrototypeDelete,
34+ MapPrototypeGet,
35+ MapPrototypeHas,
36+ MapPrototypeSet,
2937 NumberIsNaN,
3038 ObjectAssign,
3139 ObjectIs,
3240 ObjectKeys,
3341 ObjectPrototypeIsPrototypeOf,
3442 ReflectApply,
43+ ReflectHas,
44+ ReflectOwnKeys,
3545 RegExpPrototypeExec,
46+ SafeMap,
47+ SafeSet,
48+ SafeWeakSet,
3649 String,
3750 StringPrototypeIndexOf,
3851 StringPrototypeSlice,
3952 StringPrototypeSplit,
53+ SymbolIterator,
4054} = primordials ;
4155
4256const {
@@ -50,7 +64,17 @@ const {
5064} = require ( 'internal/errors' ) ;
5165const AssertionError = require ( 'internal/assert/assertion_error' ) ;
5266const { inspect } = require ( 'internal/util/inspect' ) ;
53- const { isPromise, isRegExp } = require ( 'internal/util/types' ) ;
67+ const { Buffer } = require ( 'buffer' ) ;
68+ const {
69+ isKeyObject,
70+ isPromise,
71+ isRegExp,
72+ isMap,
73+ isSet,
74+ isDate,
75+ isWeakSet,
76+ isWeakMap,
77+ } = require ( 'internal/util/types' ) ;
5478const { isError, deprecate } = require ( 'internal/util' ) ;
5579const { innerOk } = require ( 'internal/assert/utils' ) ;
5680
@@ -261,6 +285,7 @@ assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
261285 throw new ERR_MISSING_ARGS ( 'actual' , 'expected' ) ;
262286 }
263287 if ( isDeepEqual === undefined ) lazyLoadComparison ( ) ;
288+ // if (!compareBranch(actual, expected) || !compareBranch(expected, actual)) {
264289 if ( ! isDeepStrictEqual ( actual , expected ) ) {
265290 innerFail ( {
266291 actual,
@@ -341,6 +366,190 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
341366 }
342367} ;
343368
369+ function isSpecial ( obj ) {
370+ return obj == null || typeof obj !== 'object' || isError ( obj ) || isRegExp ( obj ) || isDate ( obj ) ;
371+ }
372+
373+ const typesToCallDeepStrictEqualWith = [
374+ isKeyObject , isWeakSet , isWeakMap , Buffer . isBuffer , ArrayBufferIsView ,
375+ ] ;
376+
377+ /**
378+ * Compares two objects or values recursively to check if they are equal.
379+ * @param {any } actual - The actual value to compare.
380+ * @param {any } expected - The expected value to compare.
381+ * @param {Set } [comparedObjects=new Set()] - Set to track compared objects for handling circular references.
382+ * @returns {boolean } - Returns `true` if the actual value matches the expected value, otherwise `false`.
383+ * @example
384+ * compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true
385+ */
386+ function compareBranch (
387+ actual ,
388+ expected ,
389+ comparedObjects ,
390+ ) {
391+ // Check for Map object equality
392+ if ( isMap ( actual ) && isMap ( expected ) ) {
393+ if ( actual . size !== expected . size ) {
394+ return false ;
395+ }
396+ const safeIterator = FunctionPrototypeCall ( SafeMap . prototype [ SymbolIterator ] , actual ) ;
397+
398+ comparedObjects ??= new SafeWeakSet ( ) ;
399+
400+ for ( const { 0 : key , 1 : val } of safeIterator ) {
401+ if ( ! MapPrototypeHas ( expected , key ) ) {
402+ return false ;
403+ }
404+ if ( ! compareBranch ( val , MapPrototypeGet ( expected , key ) , comparedObjects ) ) {
405+ return false ;
406+ }
407+ }
408+ return true ;
409+ }
410+
411+ for ( const type of typesToCallDeepStrictEqualWith ) {
412+ if ( type ( actual ) || type ( expected ) ) {
413+ if ( isDeepStrictEqual === undefined ) lazyLoadComparison ( ) ;
414+ return isDeepStrictEqual ( actual , expected ) ;
415+ }
416+ }
417+
418+ // Check for Set object equality
419+ // TODO(aduh95): switch to `SetPrototypeIsSubsetOf` when it's available
420+ if ( isSet ( actual ) && isSet ( expected ) ) {
421+ if ( expected . size > actual . size ) {
422+ return false ; // `expected` can't be a subset if it has more elements
423+ }
424+
425+ if ( isDeepEqual === undefined ) lazyLoadComparison ( ) ;
426+
427+ const actualArray = ArrayFrom ( actual ) ;
428+ const expectedArray = ArrayFrom ( expected ) ;
429+ const usedIndices = new SafeSet ( ) ;
430+
431+ for ( let expectedIdx = 0 ; expectedIdx < expectedArray . length ; expectedIdx ++ ) {
432+ const expectedItem = expectedArray [ expectedIdx ] ;
433+ let found = false ;
434+
435+ for ( let actualIdx = 0 ; actualIdx < actualArray . length ; actualIdx ++ ) {
436+ if ( ! usedIndices . has ( actualIdx ) && isDeepStrictEqual ( actualArray [ actualIdx ] , expectedItem ) ) {
437+ usedIndices . add ( actualIdx ) ;
438+ found = true ;
439+ break ;
440+ }
441+ }
442+
443+ if ( ! found ) {
444+ return false ;
445+ }
446+ }
447+
448+ return true ;
449+ }
450+
451+ // Check if expected array is a subset of actual array
452+ if ( ArrayIsArray ( actual ) && ArrayIsArray ( expected ) ) {
453+ if ( expected . length > actual . length ) {
454+ return false ;
455+ }
456+
457+ if ( isDeepEqual === undefined ) lazyLoadComparison ( ) ;
458+
459+ // Create a map to count occurrences of each element in the expected array
460+ const expectedCounts = new SafeMap ( ) ;
461+ for ( const expectedItem of expected ) {
462+ let found = false ;
463+ for ( const { 0 : key , 1 : count } of expectedCounts ) {
464+ if ( isDeepStrictEqual ( key , expectedItem ) ) {
465+ MapPrototypeSet ( expectedCounts , key , count + 1 ) ;
466+ found = true ;
467+ break ;
468+ }
469+ }
470+ if ( ! found ) {
471+ MapPrototypeSet ( expectedCounts , expectedItem , 1 ) ;
472+ }
473+ }
474+
475+ // Create a map to count occurrences of relevant elements in the actual array
476+ for ( const actualItem of actual ) {
477+ for ( const { 0 : key , 1 : count } of expectedCounts ) {
478+ if ( isDeepStrictEqual ( key , actualItem ) ) {
479+ if ( count === 1 ) {
480+ MapPrototypeDelete ( expectedCounts , key ) ;
481+ } else {
482+ MapPrototypeSet ( expectedCounts , key , count - 1 ) ;
483+ }
484+ break ;
485+ }
486+ }
487+ }
488+
489+ return ! expectedCounts . size ;
490+ }
491+
492+ // Comparison done when at least one of the values is not an object
493+ if ( isSpecial ( actual ) || isSpecial ( expected ) ) {
494+ if ( isDeepEqual === undefined ) {
495+ lazyLoadComparison ( ) ;
496+ }
497+ return isDeepStrictEqual ( actual , expected ) ;
498+ }
499+
500+ // Use Reflect.ownKeys() instead of Object.keys() to include symbol properties
501+ const keysExpected = ReflectOwnKeys ( expected ) ;
502+
503+ comparedObjects ??= new SafeWeakSet ( ) ;
504+
505+ // Handle circular references
506+ if ( comparedObjects . has ( actual ) ) {
507+ return true ;
508+ }
509+ comparedObjects . add ( actual ) ;
510+
511+ // Check if all expected keys and values match
512+ for ( let i = 0 ; i < keysExpected . length ; i ++ ) {
513+ const key = keysExpected [ i ] ;
514+ assert (
515+ ReflectHas ( actual , key ) ,
516+ new AssertionError ( { message : `Expected key ${ String ( key ) } not found in actual object` } ) ,
517+ ) ;
518+ if ( ! compareBranch ( actual [ key ] , expected [ key ] , comparedObjects ) ) {
519+ return false ;
520+ }
521+ }
522+
523+ return true ;
524+ }
525+
526+ /**
527+ * The strict equivalence assertion test between two objects
528+ * @param {any } actual
529+ * @param {any } expected
530+ * @param {string | Error } [message]
531+ * @returns {void }
532+ */
533+ assert . partialDeepStrictEqual = function partialDeepStrictEqual (
534+ actual ,
535+ expected ,
536+ message ,
537+ ) {
538+ if ( arguments . length < 2 ) {
539+ throw new ERR_MISSING_ARGS ( 'actual' , 'expected' ) ;
540+ }
541+
542+ if ( ! compareBranch ( actual , expected ) ) {
543+ innerFail ( {
544+ actual,
545+ expected,
546+ message,
547+ operator : 'partialDeepStrictEqual' ,
548+ stackStartFn : partialDeepStrictEqual ,
549+ } ) ;
550+ }
551+ } ;
552+
344553class Comparison {
345554 constructor ( obj , keys , actual ) {
346555 for ( const key of keys ) {
0 commit comments