5
5
* LICENSE file in the root directory of this source tree.
6
6
*/
7
7
8
- const PROTECT_PROPERTY = Symbol . for ( '$$jest-protect-from-deletion' ) ;
8
+ const PROTECT_SYMBOL = Symbol . for ( '$$jest-protect-from-deletion' ) ;
9
9
10
10
/**
11
11
* 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');
15
15
*/
16
16
export function deleteProperties ( value : unknown ) : void {
17
17
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 ) ;
24
25
}
25
26
}
26
27
}
@@ -31,15 +32,38 @@ export function deleteProperties(value: unknown): void {
31
32
*
32
33
* @param value The given value.
33
34
* @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.
36
40
*/
37
- export function protectProperties < T extends object > (
41
+ export function protectProperties < T > (
38
42
value : T ,
39
43
properties : Array < keyof T > = [ ] ,
44
+ depth = 2 ,
40
45
) : 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 originalEmitWarning = process . emitWarning ;
54
+ try {
55
+ // Reflect.get may cause deprecation warnings, so we disable them temporarily
56
+ process . emitWarning = ( ) => { } ;
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 . emitWarning = originalEmitWarning ;
64
+ }
65
+ }
66
+ return result ;
43
67
}
44
68
return false ;
45
69
}
@@ -57,3 +81,15 @@ export function canDeleteProperties(value: unknown): value is object {
57
81
58
82
return false ;
59
83
}
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