UNPKG

jest-environment-node

Version:
340 lines (329 loc) 10.6 kB
/*! * /** * * 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. * * / */ /******/ (() => { // webpackBootstrap /******/ "use strict"; var __webpack_exports__ = {}; // This entry needs to be wrapped in an IIFE because it uses a non-standard name for the exports (exports). (() => { var exports = __webpack_exports__; Object.defineProperty(exports, "__esModule", ({ value: true })); exports["default"] = exports.TestEnvironment = void 0; function _vm() { const data = require("vm"); _vm = function () { return data; }; return data; } function _fakeTimers() { const data = require("@jest/fake-timers"); _fakeTimers = function () { return data; }; return data; } function _jestMock() { const data = require("jest-mock"); _jestMock = function () { return data; }; return data; } function _jestUtil() { const data = require("jest-util"); _jestUtil = function () { return data; }; return data; } function _jestValidate() { const data = require("jest-validate"); _jestValidate = function () { return data; }; return data; } /** * 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. */ // some globals we do not want, either because deprecated or we set it ourselves const denyList = new Set(['GLOBAL', 'root', 'global', 'globalThis', 'Buffer', 'ArrayBuffer', 'Uint8Array', // if env is loaded within a jest test 'jest-symbol-do-not-touch']); const nodeGlobals = new Map(Object.getOwnPropertyNames(globalThis).filter(global => !denyList.has(global)).map(nodeGlobalsKey => { const descriptor = Object.getOwnPropertyDescriptor(globalThis, nodeGlobalsKey); if (!descriptor) { throw new Error(`No property descriptor for ${nodeGlobalsKey}, this is a bug in Jest.`); } return [nodeGlobalsKey, descriptor]; })); function isString(value) { return typeof value === 'string'; } const timerIdToRef = id => ({ id, ref() { return this; }, unref() { return this; } }); const timerRefToId = timer => timer?.id; class NodeEnvironment { context; fakeTimers; fakeTimersModern; global; moduleMocker; customExportConditions = ['node', 'node-addons']; _configuredExportConditions; _globalProxy; // while `context` is unused, it should always be passed constructor(config, _context) { const { projectConfig } = config; const globalsCleanupMode = readGlobalsCleanupConfig(projectConfig); (0, _jestUtil().initializeGarbageCollectionUtils)(globalThis, globalsCleanupMode); this._globalProxy = new GlobalProxy(); this.context = (0, _vm().createContext)(this._globalProxy.proxy()); const global = (0, _vm().runInContext)('this', Object.assign(this.context, projectConfig.testEnvironmentOptions)); this.global = global; const contextGlobals = new Set(Object.getOwnPropertyNames(global)); for (const [nodeGlobalsKey, descriptor] of nodeGlobals) { (0, _jestUtil().protectProperties)(globalThis[nodeGlobalsKey]); if (!contextGlobals.has(nodeGlobalsKey)) { if (descriptor.configurable) { Object.defineProperty(global, nodeGlobalsKey, { configurable: true, enumerable: descriptor.enumerable, get() { const value = globalThis[nodeGlobalsKey]; // override lazy getter Object.defineProperty(global, nodeGlobalsKey, { configurable: true, enumerable: descriptor.enumerable, value, writable: true }); return value; }, set(value) { // override lazy getter Object.defineProperty(global, nodeGlobalsKey, { configurable: true, enumerable: descriptor.enumerable, value, writable: true }); } }); } else if ('value' in descriptor) { Object.defineProperty(global, nodeGlobalsKey, { configurable: false, enumerable: descriptor.enumerable, value: descriptor.value, writable: descriptor.writable }); } else { Object.defineProperty(global, nodeGlobalsKey, { configurable: false, enumerable: descriptor.enumerable, get: descriptor.get, set: descriptor.set }); } } } global.global = global; global.Buffer = Buffer; global.ArrayBuffer = ArrayBuffer; // TextEncoder (global or via 'util') references a Uint8Array constructor // different than the global one used by users in tests. This makes sure the // same constructor is referenced by both. global.Uint8Array = Uint8Array; (0, _jestUtil().installCommonGlobals)(global, projectConfig.globals, globalsCleanupMode); if ('asyncDispose' in Symbol && !('asyncDispose' in global.Symbol)) { const globalSymbol = global.Symbol; // @ts-expect-error - it's readonly - but we have checked above that it's not there globalSymbol.asyncDispose = globalSymbol.for('nodejs.asyncDispose'); // @ts-expect-error - it's readonly - but we have checked above that it's not there globalSymbol.dispose = globalSymbol.for('nodejs.dispose'); } // Node's error-message stack size is limited at 10, but it's pretty useful // to see more than that when a test fails. global.Error.stackTraceLimit = 100; if ('customExportConditions' in projectConfig.testEnvironmentOptions) { const { customExportConditions } = projectConfig.testEnvironmentOptions; if (Array.isArray(customExportConditions) && customExportConditions.every(isString)) { this._configuredExportConditions = customExportConditions; } else { throw new Error('Custom export conditions specified but they are not an array of strings'); } } this.moduleMocker = new (_jestMock().ModuleMocker)(global); this.fakeTimers = new (_fakeTimers().LegacyFakeTimers)({ config: projectConfig, global, moduleMocker: this.moduleMocker, timerConfig: { idToRef: timerIdToRef, refToId: timerRefToId } }); this.fakeTimersModern = new (_fakeTimers().ModernFakeTimers)({ config: projectConfig, global }); this._globalProxy.envSetupCompleted(); } // eslint-disable-next-line @typescript-eslint/no-empty-function async setup() {} async teardown() { if (this.fakeTimers) { this.fakeTimers.dispose(); } if (this.fakeTimersModern) { this.fakeTimersModern.dispose(); } this.context = null; this.fakeTimers = null; this.fakeTimersModern = null; this._globalProxy.clear(); } exportConditions() { return this._configuredExportConditions ?? this.customExportConditions; } getVmContext() { return this.context; } } exports["default"] = NodeEnvironment; const TestEnvironment = exports.TestEnvironment = NodeEnvironment; /** * Creates a new empty global object and wraps it with a {@link Proxy}. * * The purpose is to register any property set on the global object, * and {@link #deleteProperties} on them at environment teardown, * to clean up memory and prevent leaks. */ class GlobalProxy { global = Object.create(Object.getPrototypeOf(globalThis)); globalProxy = new Proxy(this.global, this); isEnvSetup = false; propertyToValue = new Map(); leftovers = []; constructor() { this.register = this.register.bind(this); } proxy() { return this.globalProxy; } /** * Marks that the environment setup has completed, and properties set on * the global object from now on should be deleted at teardown. */ envSetupCompleted() { this.isEnvSetup = true; } /** * 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}. */ clear() { for (const { value } of [...[...this.propertyToValue.entries()].map(([property, value]) => ({ property, value })), ...this.leftovers]) { (0, _jestUtil().deleteProperties)(value); } this.propertyToValue.clear(); this.leftovers = []; this.global = {}; this.globalProxy = {}; } defineProperty(target, property, attributes) { const newAttributes = { ...attributes }; if ('set' in newAttributes && newAttributes.set !== undefined) { const originalSet = newAttributes.set; const register = this.register; newAttributes.set = value => { originalSet(value); const newValue = Reflect.get(target, property); register(property, newValue); }; } const result = Reflect.defineProperty(target, property, newAttributes); if ('value' in newAttributes) { this.register(property, newAttributes.value); } return result; } deleteProperty(target, property) { const result = Reflect.deleteProperty(target, property); const value = this.propertyToValue.get(property); if (value) { this.leftovers.push({ property, value }); this.propertyToValue.delete(property); } return result; } register(property, value) { const currentValue = this.propertyToValue.get(property); if (value !== currentValue) { if (!this.isEnvSetup && (0, _jestUtil().canDeleteProperties)(value)) { (0, _jestUtil().protectProperties)(value); } if (currentValue) { this.leftovers.push({ property, value: currentValue }); } this.propertyToValue.set(property, value); } } } function readGlobalsCleanupConfig(projectConfig) { const rawConfig = projectConfig.testEnvironmentOptions.globalsCleanup; const config = rawConfig?.toString()?.toLowerCase(); switch (config) { case 'off': case 'on': case 'soft': return config; default: { if (config !== undefined) { (0, _jestValidate().logValidationWarning)('testEnvironmentOptions.globalsCleanup', `Unknown value given: ${rawConfig}`, 'Available options are: [on, soft, off]'); } return 'soft'; } } } })(); module.exports = __webpack_exports__; /******/ })() ;