@@ -15,11 +15,13 @@ import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
1515import type { Global } from '@jest/types' ;
1616import { ModuleMocker } from 'jest-mock' ;
1717import {
18+ type DeletionMode ,
1819 canDeleteProperties ,
1920 deleteProperties ,
2021 installCommonGlobals ,
2122 protectProperties ,
2223} from 'jest-util' ;
24+ import { logValidationWarning } from 'jest-validate' ;
2325
2426type Timer = {
2527 id : number ;
@@ -86,7 +88,7 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
8688 customExportConditions = [ 'node' , 'node-addons' ] ;
8789 private readonly _configuredExportConditions ?: Array < string > ;
8890 private _globalProxy : GlobalProxy ;
89- private _clearGlobalsAtShutdown : boolean ;
91+ private _globalsCleanupMode : 'off' | DeletionMode ;
9092
9193 // while `context` is unused, it should always be passed
9294 constructor ( config : JestEnvironmentConfig , _context : EnvironmentContext ) {
@@ -204,9 +206,27 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
204206 } ) ;
205207
206208 this . _globalProxy . envSetupCompleted ( ) ;
207- this . _clearGlobalsAtShutdown = ! [ true , 'true' ] . includes (
208- projectConfig . testEnvironmentOptions [ 'disableGlobalsCleanup' ] as any ,
209- ) ;
209+ this . _globalsCleanupMode = ( ( ) => {
210+ const rawConfig =
211+ projectConfig . testEnvironmentOptions [ 'globalsCleanupMode' ] ;
212+ const config = rawConfig ?. toString ( ) ?. toLowerCase ( ) ;
213+ switch ( config ) {
214+ case 'hard' :
215+ case 'soft' :
216+ case 'off' :
217+ return config ;
218+ default : {
219+ if ( config !== undefined ) {
220+ logValidationWarning (
221+ 'testEnvironmentOptions.globalsCleanupMode' ,
222+ `Unknown value given: ${ rawConfig } ` ,
223+ 'Available options are: [hard, soft, off]' ,
224+ ) ;
225+ }
226+ return 'soft' ;
227+ }
228+ }
229+ } ) ( ) ;
210230 }
211231
212232 // eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -222,8 +242,8 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
222242 this . context = null ;
223243 this . fakeTimers = null ;
224244 this . fakeTimersModern = null ;
225- if ( this . _clearGlobalsAtShutdown ) {
226- this . _globalProxy . clear ( ) ;
245+ if ( this . _globalsCleanupMode !== 'off' ) {
246+ this . _globalProxy . clear ( this . _globalsCleanupMode ) ;
227247 }
228248 }
229249
@@ -274,16 +294,18 @@ class GlobalProxy implements ProxyHandler<typeof globalThis> {
274294 * Deletes any property that was set on the global object, except for:
275295 * 1. Properties that were set before {@link #envSetupCompleted} was invoked.
276296 * 2. Properties protected by {@link #protectProperties}.
297+ *
298+ * @param mode determines whether to soft or hard delete the properties.
277299 */
278- clear ( ) : void {
300+ clear ( mode : DeletionMode ) : void {
279301 for ( const { value} of [
280302 ...[ ...this . propertyToValue . entries ( ) ] . map ( ( [ property , value ] ) => ( {
281303 property,
282304 value,
283305 } ) ) ,
284306 ...this . leftovers ,
285307 ] ) {
286- deleteProperties ( value ) ;
308+ deleteProperties ( value , mode ) ;
287309 }
288310 this . propertyToValue . clear ( ) ;
289311 this . leftovers = [ ] ;
0 commit comments