Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
id: cpu-cores
uses: SimenB/github-actions-cpu-cores@97ba232459a8e02ff6121db9362b09661c875ab8 # v2.0.0
- name: run node-env tests
run: yarn test-node-env
run: yarn workspace jest-environment-node test
- name: run tests
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
id: cpu-cores
uses: SimenB/github-actions-cpu-cores@97ba232459a8e02ff6121db9362b09661c875ab8 # v2.0.0
- name: run node-env tests
run: yarn test-node-env
run: yarn workspace jest-environment-node test
- name: run tests
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
with:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Fixes

- `[jest-resolver]` Resolve builtin modules correctly ([#15683](https://github.com/jestjs/jest/pull/15683))
- `[jest-environment-node, jest-util]` Avoid setting globals cleanup protection symbol when feature is off ([#15684](https://github.com/jestjs/jest/pull/15684))

### Chore & Maintenance

Expand Down
2 changes: 1 addition & 1 deletion jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default {
},
snapshotSerializers: [require.resolve('jest-serializer-ansi-escapes')],
testEnvironmentOptions: {
globalsCleanup: 'on',
globalsCleanup: process.env.GLOBALS_CLEANUP ?? 'on',
},
testPathIgnorePatterns: [
'/__arbitraries__/',
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@
"test-ts": "yarn jest --config jest.config.ts.mjs",
"test-types": "yarn tstyche",
"test-with-type-info": "yarn jest e2e/__tests__/jest.config.ts.test.ts",
"test-node-env": "yarn jest packages/jest-environment-node/src/__tests__",
"test": "yarn lint && yarn jest",
"typecheck": "yarn typecheck:examples && yarn typecheck:tests",
"typecheck:examples": "tsc -p examples/expect-extend && tsc -p examples/typescript",
Expand Down
7 changes: 7 additions & 0 deletions packages/jest-environment-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
"@jest/test-utils": "workspace:*",
"clsx": "^2.1.1"
},
"scripts": {
"test:base": "echo GLOBALS_CLEANUP=$GLOBALS_CLEANUP && yarn --cwd='../.' jest --runInBand packages/jest-environment-node/src/__tests__",
"test:globals-cleanup-off": "GLOBALS_CLEANUP=off yarn test:base",
"test:globals-cleanup-soft": "GLOBALS_CLEANUP=soft yarn test:base",
"test:globals-cleanup-on": "GLOBALS_CLEANUP=on yarn test:base",
"test": "yarn test:globals-cleanup-off && yarn test:globals-cleanup-soft && yarn test:globals-cleanup-on"
},
"engines": {
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {AsyncLocalStorage, createHook} from 'async_hooks';
import {clsx} from 'clsx';
import {onNodeVersions} from '@jest/test-utils';

describe('NodeEnvironment 2', () => {
describe('Globals Cleanup 1', () => {
test('dispatch event', () => {
new EventTarget().dispatchEvent(new Event('foo'));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {AsyncLocalStorage, createHook} from 'async_hooks';
import {clsx} from 'clsx';
import {onNodeVersions} from '@jest/test-utils';

describe('Globals Cleanup 2', () => {
test('dispatch event', () => {
new EventTarget().dispatchEvent(new Event('foo'));
});

test('set modules on global', () => {
(globalThis as any).async_hooks = require('async_hooks');
(globalThis as any).AsyncLocalStorage =
require('async_hooks').AsyncLocalStorage;
(globalThis as any).createHook = require('async_hooks').createHook;
(globalThis as any).clsx = require('clsx');
expect(AsyncLocalStorage).toBeDefined();
expect(clsx).toBeDefined();
expect(createHook).toBeDefined();
expect(createHook({})).toBeDefined();
expect(clsx()).toBeDefined();
});

onNodeVersions('>=19.8.0', () => {
test('use static function from core module set on global', () => {
expect(AsyncLocalStorage.snapshot).toBeDefined();
expect(AsyncLocalStorage.snapshot()).toBeDefined();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

function onlyIfGlobalsCleanup(
globalsCleanup: string,
testBody: () => void,
): void {
const describeFunc =
process.env.GLOBALS_CLEANUP === globalsCleanup ? describe : describe.skip;
describeFunc(`GLOBALS_CLEANUP=${globalsCleanup}`, testBody);
}

describe('Globals Cleanup 3', () => {
onlyIfGlobalsCleanup('off', () => {
test('assign Object prototype descriptors to a new empty object', () => {
const descriptors = Object.getOwnPropertyDescriptors(
Object.getPrototypeOf({}),
);
Object.assign({}, descriptors);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@
*/

import type {EnvironmentContext} from '@jest/environment';
import {
makeGlobalConfig,
makeProjectConfig,
onNodeVersions,
} from '@jest/test-utils';
import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils';
import NodeEnvironment from '../';
import {AsyncLocalStorage, createHook} from 'async_hooks';
import {clsx} from 'clsx';

const context: EnvironmentContext = {
console,
Expand Down Expand Up @@ -93,28 +87,4 @@ describe('NodeEnvironment', () => {
test('TextEncoder references the same global Uint8Array constructor', () => {
expect(new TextEncoder().encode('abc')).toBeInstanceOf(Uint8Array);
});

test('dispatch event', () => {
new EventTarget().dispatchEvent(new Event('foo'));
});

test('set modules on global', () => {
(globalThis as any).async_hooks = require('async_hooks');
(globalThis as any).AsyncLocalStorage =
require('async_hooks').AsyncLocalStorage;
(globalThis as any).createHook = require('async_hooks').createHook;
(globalThis as any).clsx = require('clsx');
expect(AsyncLocalStorage).toBeDefined();
expect(clsx).toBeDefined();
expect(createHook).toBeDefined();
expect(createHook({})).toBeDefined();
expect(clsx()).toBeDefined();
});

onNodeVersions('>=19.8.0', () => {
test('use static function from core module set on global', () => {
expect(AsyncLocalStorage.snapshot).toBeDefined();
expect(AsyncLocalStorage.snapshot()).toBeDefined();
});
});
});
63 changes: 33 additions & 30 deletions packages/jest-environment-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
JestEnvironmentConfig,
} from '@jest/environment';
import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Global} from '@jest/types';
import type {Config, Global} from '@jest/types';
import {ModuleMocker} from 'jest-mock';
import {
type DeletionMode,
canDeleteProperties,
deleteProperties,
initializeGarbageCollectionUtils,
installCommonGlobals,
protectProperties,
} from 'jest-util';
Expand Down Expand Up @@ -88,11 +89,14 @@
customExportConditions = ['node', 'node-addons'];
private readonly _configuredExportConditions?: Array<string>;
private _globalProxy: GlobalProxy;
private _globalsCleanup: DeletionMode;

// while `context` is unused, it should always be passed
constructor(config: JestEnvironmentConfig, _context: EnvironmentContext) {
const {projectConfig} = config;

const globalsCleanupMode = readGlobalsCleanupConfig(projectConfig);
initializeGarbageCollectionUtils(globalThis, globalsCleanupMode);

this._globalProxy = new GlobalProxy();
this.context = createContext(this._globalProxy.proxy());
const global = runInContext(
Expand Down Expand Up @@ -160,7 +164,7 @@
// same constructor is referenced by both.
global.Uint8Array = Uint8Array;

installCommonGlobals(global, projectConfig.globals);
installCommonGlobals(global, projectConfig.globals, globalsCleanupMode);

if ('asyncDispose' in Symbol && !('asyncDispose' in global.Symbol)) {
const globalSymbol = global.Symbol as unknown as SymbolConstructor;
Expand Down Expand Up @@ -206,26 +210,6 @@
});

this._globalProxy.envSetupCompleted();
this._globalsCleanup = (() => {
const rawConfig = projectConfig.testEnvironmentOptions.globalsCleanup;
const config = rawConfig?.toString()?.toLowerCase();
switch (config) {
case 'off':
case 'on':
case 'soft':
return config;
default: {
if (config !== undefined) {
logValidationWarning(
'testEnvironmentOptions.globalsCleanup',
`Unknown value given: ${rawConfig}`,
'Available options are: [on, soft, off]',
);
}
return 'soft';
}
}
})();
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand All @@ -241,9 +225,7 @@
this.context = null;
this.fakeTimers = null;
this.fakeTimersModern = null;
if (this._globalsCleanup !== 'off') {
this._globalProxy.clear(this._globalsCleanup);
}
this._globalProxy.clear();

Check warning on line 228 in packages/jest-environment-node/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-environment-node/src/index.ts#L228

Added line #L228 was not covered by tests
}

exportConditions(): Array<string> {
Expand Down Expand Up @@ -293,18 +275,16 @@
* Deletes any property that was set on the global object, except for:
* 1. Properties that were set before {@link #envSetupCompleted} was invoked.
* 2. Properties protected by {@link #protectProperties}.
*
* @param mode determines whether to soft or hard delete the properties.
*/
clear(mode: DeletionMode): void {
clear(): void {

Check warning on line 279 in packages/jest-environment-node/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-environment-node/src/index.ts#L279

Added line #L279 was not covered by tests
for (const {value} of [
...[...this.propertyToValue.entries()].map(([property, value]) => ({
property,
value,
})),
...this.leftovers,
]) {
deleteProperties(value, mode);
deleteProperties(value);

Check warning on line 287 in packages/jest-environment-node/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-environment-node/src/index.ts#L287

Added line #L287 was not covered by tests
}
this.propertyToValue.clear();
this.leftovers = [];
Expand Down Expand Up @@ -365,3 +345,26 @@
}
}
}

function readGlobalsCleanupConfig(
projectConfig: Config.ProjectConfig,
): DeletionMode {
const rawConfig = projectConfig.testEnvironmentOptions.globalsCleanup;
const config = rawConfig?.toString()?.toLowerCase();
switch (config) {
case 'off':
case 'on':
case 'soft':
return config;

Check warning on line 358 in packages/jest-environment-node/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-environment-node/src/index.ts#L355-L358

Added lines #L355 - L358 were not covered by tests
default: {
if (config !== undefined) {
logValidationWarning(

Check warning on line 361 in packages/jest-environment-node/src/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-environment-node/src/index.ts#L361

Added line #L361 was not covered by tests
'testEnvironmentOptions.globalsCleanup',
`Unknown value given: ${rawConfig}`,
'Available options are: [on, soft, off]',
);
}
return 'soft';
}
}
}
Loading
Loading