@@ -24,6 +24,11 @@ const { DEBUG } = process.env
2424// This is set by the setShouldKill function
2525let shouldKill
2626
27+ // Symbol constants for dynamic value matching in assertObjectContains
28+ const ANY_STRING = Symbol ( 'test.ANY_STRING' )
29+ const ANY_NUMBER = Symbol ( 'test.ANY_NUMBER' )
30+ const ANY_VALUE = Symbol ( 'test.ANY_VALUE' )
31+
2732/**
2833 * @param {string } filename
2934 * @param {string } cwd
@@ -635,9 +640,8 @@ function setShouldKill (value) {
635640 } )
636641}
637642
638- // @ts -expect-error assert.partialDeepStrictEqual does not exist on older Node.js versions
639- // eslint-disable-next-line n/no-unsupported-features/node-builtins
640- const assertObjectContains = assert . partialDeepStrictEqual || function assertObjectContains ( actual , expected , msg ) {
643+ // Implementation with optional matcher support (ANY_STRING, ANY_NUMBER, ANY_VALUE)
644+ function assertObjectContainsImpl ( actual , expected , msg , useMatchers ) {
641645 if ( expected === null || typeof expected !== 'object' ) {
642646 assert . strictEqual ( actual , expected , msg )
643647 return
@@ -652,7 +656,7 @@ const assertObjectContains = assert.partialDeepStrictEqual || function assertObj
652656 const actualItem = actual [ i ]
653657 try {
654658 if ( expectedItem !== null && typeof expectedItem === 'object' ) {
655- assertObjectContains ( actualItem , expectedItem , msg )
659+ assertObjectContainsImpl ( actualItem , expectedItem , msg , useMatchers )
656660 } else {
657661 assert . strictEqual ( actualItem , expectedItem , msg )
658662 }
@@ -670,15 +674,40 @@ const assertObjectContains = assert.partialDeepStrictEqual || function assertObj
670674
671675 for ( const [ key , val ] of Object . entries ( expected ) ) {
672676 assert . ok ( Object . hasOwn ( actual , key ) , msg )
673- if ( val !== null && typeof val === 'object' ) {
674- assertObjectContains ( actual [ key ] , val , msg )
677+ if ( useMatchers && val === ANY_STRING ) {
678+ assert . strictEqual ( typeof actual [ key ] , 'string' , `Expected ${ key } to be a string but got ${ typeof actual [ key ] } ` )
679+ } else if ( useMatchers && val === ANY_NUMBER ) {
680+ assert . strictEqual ( typeof actual [ key ] , 'number' , `Expected ${ key } to be a number but got ${ typeof actual [ key ] } ` )
681+ } else if ( useMatchers && val === ANY_VALUE ) {
682+ assert . ok ( actual [ key ] !== undefined , `Expected ${ key } to be present but it was undefined` )
683+ } else if ( val !== null && typeof val === 'object' ) {
684+ assertObjectContainsImpl ( actual [ key ] , val , msg , useMatchers )
675685 } else {
676686 assert . ok ( actual , msg )
677687 assert . strictEqual ( actual [ key ] , expected [ key ] , msg )
678688 }
679689 }
680690}
681691
692+ // Main assertObjectContains: tries partialDeepStrictEqual or strict first, falls back to matchers
693+ const assertObjectContains = function assertObjectContains ( actual , expected , msg ) {
694+ // @ts -expect-error assert.partialDeepStrictEqual does not exist on older Node.js versions
695+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
696+ const assertionFn = assert . partialDeepStrictEqual ||
697+ ( ( actual , expected , msg ) => assertObjectContainsImpl ( actual , expected , msg , false ) )
698+
699+ try {
700+ assertionFn ( actual , expected , msg )
701+ } catch ( originalError ) {
702+ // First attempt failed, retry with matcher support
703+ try {
704+ assertObjectContainsImpl ( actual , expected , msg , true )
705+ } catch {
706+ throw originalError
707+ }
708+ }
709+ }
710+
682711/**
683712 * @param {string } actual
684713 * @param {string } [msg]
@@ -688,6 +717,9 @@ function assertUUID (actual, msg = 'not a valid UUID') {
688717}
689718
690719module . exports = {
720+ ANY_NUMBER ,
721+ ANY_STRING ,
722+ ANY_VALUE ,
691723 FakeAgent,
692724 hookFile,
693725 assertObjectContains,
0 commit comments