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,32 @@ 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 By default, this function protects only the top-most properties of the given value.
38+ * To protect nested properties as well, set this to a number greater than 0.
3639 */
37- export function protectProperties < T extends object > (
40+ export function protectProperties < T > (
3841 value : T ,
3942 properties : Array < keyof T > = [ ] ,
43+ depth = 0 ,
4044) : boolean {
41- if ( canDeleteProperties ( value ) ) {
42- return Reflect . set ( value , PROTECT_PROPERTY , properties ) ;
45+ if (
46+ depth >= 0 &&
47+ canDeleteProperties ( value ) &&
48+ ! Reflect . has ( value , PROTECT_SYMBOL )
49+ ) {
50+ const result = Reflect . set ( value , PROTECT_SYMBOL , properties ) ;
51+ for ( const key of getProtectedKeys ( value , properties ) ) {
52+ try {
53+ const nested = Reflect . get ( value , key ) ;
54+ protectProperties ( nested , [ ] , depth - 1 ) ;
55+ } catch {
56+ // Reflect.get might fail in certain edge-cases
57+ // Instead of failing the entire process, we will skip the property.
58+ }
59+ }
60+ return result ;
4361 }
4462 return false ;
4563}
@@ -57,3 +75,15 @@ export function canDeleteProperties(value: unknown): value is object {
5775
5876 return false ;
5977}
78+
79+ function getProtectedKeys < T extends object > (
80+ value : T ,
81+ properties : Array < keyof T > | undefined ,
82+ ) : Array < string | symbol | number > {
83+ if ( properties === undefined ) {
84+ return [ ] ;
85+ }
86+ const protectedKeys =
87+ properties . length > 0 ? properties : Reflect . ownKeys ( value ) ;
88+ return protectedKeys . filter ( key => PROTECT_SYMBOL !== key ) ;
89+ }
0 commit comments