55 * LICENSE file in the root directory of this source tree.
66 */
77
8- const PROTECT_PROPERTY = Symbol . for ( '$$jest-protect-from-deletion' ) ;
8+ const PROTECT_SYMBOL = Symbol . for ( '$$jest-protect-from-deletion' ) ;
99
1010/**
1111 * Deletes all the properties from the given value (if it's an object),
@@ -15,12 +15,13 @@ const PROTECT_PROPERTY = Symbol.for('$$jest-protect-from-deletion');
1515 */
1616export function deleteProperties ( value : unknown ) : void {
1717 if ( canDeleteProperties ( value ) ) {
18- const protectedProperties = Reflect . get ( value , PROTECT_PROPERTY ) ;
19- if ( ! Array . isArray ( protectedProperties ) || protectedProperties . length > 0 ) {
20- for ( const key of Reflect . ownKeys ( value ) ) {
21- if ( ! protectedProperties ?. includes ( key ) ) {
22- Reflect . deleteProperty ( value , key ) ;
23- }
18+ const protectedKeys = getProtectedKeys (
19+ value ,
20+ Reflect . get ( value , PROTECT_SYMBOL ) ,
21+ ) ;
22+ for ( const key of Reflect . ownKeys ( value ) ) {
23+ if ( ! protectedKeys . includes ( key ) && key !== PROTECT_SYMBOL ) {
24+ Reflect . deleteProperty ( value , key ) ;
2425 }
2526 }
2627 }
@@ -31,15 +32,38 @@ export function deleteProperties(value: unknown): void {
3132 *
3233 * @param value The given value.
3334 * @param properties If the array contains any property,
34- * then only these properties will not be deleted; otherwise if the array is empty,
35- * all properties will not be deleted.
35+ * then only these properties will be protected; otherwise if the array is empty,
36+ * all properties will be protected.
37+ * @param depth Determines how "deep" the protection should be.
38+ * A value of 0 means that only the top-most properties will be protected,
39+ * while a value larger than 0 means that deeper levels of nesting will be protected as well.
3640 */
37- export function protectProperties < T extends object > (
41+ export function protectProperties < T > (
3842 value : T ,
3943 properties : Array < keyof T > = [ ] ,
44+ depth = 2 ,
4045) : boolean {
41- if ( canDeleteProperties ( value ) ) {
42- return Reflect . set ( value , PROTECT_PROPERTY , properties ) ;
46+ if (
47+ depth >= 0 &&
48+ canDeleteProperties ( value ) &&
49+ ! Reflect . has ( value , PROTECT_SYMBOL )
50+ ) {
51+ const result = Reflect . set ( value , PROTECT_SYMBOL , properties ) ;
52+ for ( const key of getProtectedKeys ( value , properties ) ) {
53+ const originalDeprecationMode = process . noDeprecation ;
54+ try {
55+ // Reflect.get may cause deprecation warnings, so we disable them temporarily
56+ process . noDeprecation = true ;
57+ const nested = Reflect . get ( value , key ) ;
58+ protectProperties ( nested , [ ] , depth - 1 ) ;
59+ } catch {
60+ // Reflect.get might fail in certain edge-cases
61+ // Instead of failing the entire process, we will skip the property.
62+ } finally {
63+ process . noDeprecation = originalDeprecationMode ;
64+ }
65+ }
66+ return result ;
4367 }
4468 return false ;
4569}
@@ -57,3 +81,15 @@ export function canDeleteProperties(value: unknown): value is object {
5781
5882 return false ;
5983}
84+
85+ function getProtectedKeys < T extends object > (
86+ value : T ,
87+ properties : Array < keyof T > | undefined ,
88+ ) : Array < string | symbol | number > {
89+ if ( properties === undefined ) {
90+ return [ ] ;
91+ }
92+ const protectedKeys =
93+ properties . length > 0 ? properties : Reflect . ownKeys ( value ) ;
94+ return protectedKeys . filter ( key => PROTECT_SYMBOL !== key ) ;
95+ }
0 commit comments