Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jest-environment): Pass global config to Jest environment constructor #12461

Merged
merged 2 commits into from Feb 23, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -9,7 +9,9 @@
- `[jest-config, @jest/types]` Add `ci` to `GlobalConfig` ([#12378](https://github.com/facebook/jest/pull/12378))
- `[jest-environment-jsdom]` [**BREAKING**] Upgrade jsdom to 19.0.0 ([#12290](https://github.com/facebook/jest/pull/12290))
- `[jest-environment-jsdom]` [**BREAKING**] Add default `browser` condition to `exportConditions` for `jsdom` environment ([#11924](https://github.com/facebook/jest/pull/11924))
- `[jest-environment-jsdom]` [**BREAKING**] Pass global config to Jest environment constructor for `jsdom` environment ([#12461](https://github.com/facebook/jest/pull/12461))
- `[jest-environment-node]` [**BREAKING**] Add default `node` and `node-addon` conditions to `exportConditions` for `node` environment ([#11924](https://github.com/facebook/jest/pull/11924))
- `[jest-environment-node]` [**BREAKING**] Pass global config to Jest environment constructor for `node` environment ([#12461](https://github.com/facebook/jest/pull/12461))
- `[@jest/expect]` New module which extends `expect` with `jest-snapshot` matchers ([#12404](https://github.com/facebook/jest/pull/12404), [#12410](https://github.com/facebook/jest/pull/12410), [#12418](https://github.com/facebook/jest/pull/12418))
- `[@jest/expect-utils]` New module exporting utils for `expect` ([#12323](https://github.com/facebook/jest/pull/12323))
- `[jest-mock]` Improve `isMockFunction` to infer types of passed function ([#12442](https://github.com/facebook/jest/pull/12442))
Expand Down
4 changes: 3 additions & 1 deletion docs/Configuration.md
Expand Up @@ -1083,7 +1083,7 @@ test('use jsdom in this test file', () => {
});
```

You can create your own module that will be used for setting up the test environment. The module must export a class with `setup`, `teardown` and `getVmContext` methods. You can also pass variables from this module to your test suites by assigning them to `this.global` object – this will make them available in your test suites as global variables.
You can create your own module that will be used for setting up the test environment. The module must export a class with `setup`, `teardown` and `getVmContext` methods. You can also pass variables from this module to your test suites by assigning them to `this.global` object – this will make them available in your test suites as global variables. Besides, you can access [global config](https://github.com/facebook/jest/blob/491e7cb0f2daa8263caccc72d48bdce7ba759b11/packages/jest-types/src/Config.ts#L284) and [project config](https://github.com/facebook/jest/blob/491e7cb0f2daa8263caccc72d48bdce7ba759b11/packages/jest-types/src/Config.ts#L349) in its constructor.
SimenB marked this conversation as resolved.
Show resolved Hide resolved

The class may optionally expose an asynchronous `handleTestEvent` method to bind to events fired by [`jest-circus`](https://github.com/facebook/jest/tree/main/packages/jest-circus). Normally, `jest-circus` test runner would pause until a promise returned from `handleTestEvent` gets fulfilled, **except for the next events**: `start_describe_definition`, `finish_describe_definition`, `add_hook`, `add_test` or `error` (for the up-to-date list you can look at [SyncEvent type in the types definitions](https://github.com/facebook/jest/tree/main/packages/jest-types/src/Circus.ts)). That is caused by backward compatibility reasons and `process.on('unhandledRejection', callback)` signature, but that usually should not be a problem for most of the use cases.

Expand All @@ -1108,6 +1108,8 @@ const NodeEnvironment = require('jest-environment-node').default;
class CustomEnvironment extends NodeEnvironment {
constructor(config, context) {
super(config, context);
console.log(config.globalConfig);
console.log(config.projectConfig);
this.testPath = context.testPath;
this.docblockPragmas = context.docblockPragmas;
}
Expand Down
2 changes: 2 additions & 0 deletions e2e/test-environment/EsmDefaultEnvironment.js
Expand Up @@ -14,6 +14,8 @@ class Env extends NodeEnvironment {
constructor(config, options) {
super(config, options);
this.global.property = 'value';
this.global.var1 = config.globalConfig.watch;
this.global.var2 = config.projectConfig.cache;
}
}

Expand Down
2 changes: 2 additions & 0 deletions e2e/test-environment/__tests__/esmDefault.test.js
Expand Up @@ -10,4 +10,6 @@

test('access env', () => {
expect(property).toBe('value'); // eslint-disable-line no-undef
expect(var1).toBe(false); // eslint-disable-line no-undef
expect(var2).toBe(true); // eslint-disable-line no-undef
});
Expand Up @@ -5,12 +5,15 @@
* LICENSE file in the root directory of this source tree.
*/

import {makeProjectConfig} from '@jest/test-utils';
import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils';
import JSDomEnvironment from '../';

describe('JSDomEnvironment', () => {
it('should configure setTimeout/setInterval to use the browser api', () => {
const env = new JSDomEnvironment(makeProjectConfig());
const env = new JSDomEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

env.fakeTimers!.useFakeTimers();

Expand All @@ -23,19 +26,23 @@ describe('JSDomEnvironment', () => {
});

it('has modern fake timers implementation', () => {
const env = new JSDomEnvironment(makeProjectConfig());
const env = new JSDomEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

expect(env.fakeTimersModern).toBeDefined();
});

it('should respect userAgent option', () => {
const env = new JSDomEnvironment(
makeProjectConfig({
const env = new JSDomEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig({
testEnvironmentOptions: {
userAgent: 'foo',
},
}),
);
});

expect(env.dom.window.navigator.userAgent).toEqual('foo');
});
Expand All @@ -53,7 +60,10 @@ describe('JSDomEnvironment', () => {
* will be called, so please make sure the global.document is still available at this point.
*/
it('should not set the global.document to null too early', () => {
const env = new JSDomEnvironment(makeProjectConfig());
const env = new JSDomEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

const originalCloseFn = env.global.close.bind(env.global);
env.global.close = () => {
Expand Down
29 changes: 17 additions & 12 deletions packages/jest-environment-jsdom/src/index.ts
Expand Up @@ -7,9 +7,13 @@

import type {Context} from 'vm';
import {JSDOM, ResourceLoader, VirtualConsole} from 'jsdom';
import type {EnvironmentContext, JestEnvironment} from '@jest/environment';
import type {
EnvironmentContext,
JestEnvironment,
JestEnvironmentConfig,
} from '@jest/environment';
import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Config, Global} from '@jest/types';
import type {Global} from '@jest/types';
import {ModuleMocker} from 'jest-mock';
import {installCommonGlobals} from 'jest-util';

Expand All @@ -30,25 +34,26 @@ export default class JSDOMEnvironment implements JestEnvironment<number> {
private errorEventListener: ((event: Event & {error: Error}) => void) | null;
moduleMocker: ModuleMocker | null;

constructor(config: Config.ProjectConfig, options?: EnvironmentContext) {
constructor(config: JestEnvironmentConfig, options?: EnvironmentContext) {
const {projectConfig} = config;
this.dom = new JSDOM(
typeof config.testEnvironmentOptions.html === 'string'
? config.testEnvironmentOptions.html
typeof projectConfig.testEnvironmentOptions.html === 'string'
? projectConfig.testEnvironmentOptions.html
: '<!DOCTYPE html>',
{
pretendToBeVisual: true,
resources:
typeof config.testEnvironmentOptions.userAgent === 'string'
typeof projectConfig.testEnvironmentOptions.userAgent === 'string'
? new ResourceLoader({
userAgent: config.testEnvironmentOptions.userAgent,
userAgent: projectConfig.testEnvironmentOptions.userAgent,
})
: undefined,
runScripts: 'dangerously',
url: config.testURL,
url: projectConfig.testURL,
virtualConsole: new VirtualConsole().sendTo(
options?.console || console,
),
...config.testEnvironmentOptions,
...projectConfig.testEnvironmentOptions,
},
);
const global = (this.global = this.dom.window.document
Expand All @@ -64,7 +69,7 @@ export default class JSDOMEnvironment implements JestEnvironment<number> {
// Node's error-message stack size is limited at 10, but it's pretty useful
// to see more than that when a test fails.
this.global.Error.stackTraceLimit = 100;
installCommonGlobals(global as any, config.globals);
installCommonGlobals(global as any, projectConfig.globals);

// TODO: remove this ASAP, but it currently causes tests to run really slow
global.Buffer = Buffer;
Expand Down Expand Up @@ -107,14 +112,14 @@ export default class JSDOMEnvironment implements JestEnvironment<number> {
};

this.fakeTimers = new LegacyFakeTimers({
config,
config: projectConfig,
global: global as unknown as typeof globalThis,
moduleMocker: this.moduleMocker,
timerConfig,
});

this.fakeTimersModern = new ModernFakeTimers({
config,
config: projectConfig,
global: global as unknown as typeof globalThis,
});
}
Expand Down
Expand Up @@ -5,31 +5,44 @@
* LICENSE file in the root directory of this source tree.
*/

import {makeProjectConfig} from '@jest/test-utils';
import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils';
import NodeEnvironment from '../';

describe('NodeEnvironment', () => {
it('uses a copy of the process object', () => {
const env1 = new NodeEnvironment(makeProjectConfig());
const env2 = new NodeEnvironment(makeProjectConfig());
const testEnvConfig = {
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
};
const env1 = new NodeEnvironment(testEnvConfig);
const env2 = new NodeEnvironment(testEnvConfig);

expect(env1.global.process).not.toBe(env2.global.process);
});

it('exposes process.on', () => {
const env1 = new NodeEnvironment(makeProjectConfig());
const env1 = new NodeEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

expect(env1.global.process.on).not.toBe(null);
});

it('exposes global.global', () => {
const env1 = new NodeEnvironment(makeProjectConfig());
const env1 = new NodeEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

expect(env1.global.global).toBe(env1.global);
});

it('should configure setTimeout/setInterval to use the node api', () => {
const env1 = new NodeEnvironment(makeProjectConfig());
const env1 = new NodeEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

env1.fakeTimers!.useFakeTimers();

Expand All @@ -44,7 +57,10 @@ describe('NodeEnvironment', () => {
});

it('has modern fake timers implementation', () => {
const env = new NodeEnvironment(makeProjectConfig());
const env = new NodeEnvironment({
globalConfig: makeGlobalConfig(),
projectConfig: makeProjectConfig(),
});

expect(env.fakeTimersModern).toBeDefined();
});
Expand Down
18 changes: 11 additions & 7 deletions packages/jest-environment-node/src/index.ts
Expand Up @@ -6,9 +6,9 @@
*/

import {Context, createContext, runInContext} from 'vm';
import type {JestEnvironment} from '@jest/environment';
import type {JestEnvironment, JestEnvironmentConfig} from '@jest/environment';
import {LegacyFakeTimers, ModernFakeTimers} from '@jest/fake-timers';
import type {Config, Global} from '@jest/types';
import type {Global} from '@jest/types';
import {ModuleMocker} from 'jest-mock';
import {installCommonGlobals} from 'jest-util';

Expand All @@ -25,11 +25,12 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
global: Global.Global;
moduleMocker: ModuleMocker | null;

constructor(config: Config.ProjectConfig) {
constructor(config: JestEnvironmentConfig) {
const {projectConfig} = config;
this.context = createContext();
const global = (this.global = runInContext(
'this',
Object.assign(this.context, config.testEnvironmentOptions),
Object.assign(this.context, projectConfig.testEnvironmentOptions),
));
global.global = global;
global.clearInterval = clearInterval;
Expand Down Expand Up @@ -81,7 +82,7 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
global.atob = atob;
global.btoa = btoa;
}
installCommonGlobals(global, config.globals);
installCommonGlobals(global, projectConfig.globals);

this.moduleMocker = new ModuleMocker(global);

Expand All @@ -104,13 +105,16 @@ export default class NodeEnvironment implements JestEnvironment<Timer> {
};

this.fakeTimers = new LegacyFakeTimers({
config,
config: projectConfig,
global,
moduleMocker: this.moduleMocker,
timerConfig,
});

this.fakeTimersModern = new ModernFakeTimers({config, global});
this.fakeTimersModern = new ModernFakeTimers({
config: projectConfig,
global,
});
}

async setup(): Promise<void> {}
Expand Down
7 changes: 6 additions & 1 deletion packages/jest-environment/src/index.ts
Expand Up @@ -28,8 +28,13 @@ export type ModuleWrapper = (
...extraGlobals: Array<Global.Global[keyof Global.Global]>
) => unknown;

export interface JestEnvironmentConfig {
projectConfig: Config.ProjectConfig;
globalConfig: Config.GlobalConfig;
}

export declare class JestEnvironment<Timer = unknown> {
constructor(config: Config.ProjectConfig, context?: EnvironmentContext);
constructor(config: JestEnvironmentConfig, context?: EnvironmentContext);
global: Global.Global;
fakeTimers: LegacyFakeTimers<Timer> | null;
fakeTimersModern: ModernFakeTimers | null;
Expand Down
21 changes: 13 additions & 8 deletions packages/jest-repl/src/cli/runtime-cli.ts
Expand Up @@ -63,22 +63,27 @@ export async function run(
const options = await readConfig(argv, root);
const globalConfig = options.globalConfig;
// Always disable automocking in scripts.
const config: Config.ProjectConfig = {
const projectConfig: Config.ProjectConfig = {
...options.projectConfig,
automock: false,
};

try {
const hasteMap = await Runtime.createContext(config, {
const hasteMap = await Runtime.createContext(projectConfig, {
maxWorkers: Math.max(cpus().length - 1, 1),
watchman: globalConfig.watchman,
});

const transformer = await createScriptTransformer(config);
const transformer = await createScriptTransformer(projectConfig);
const Environment: typeof JestEnvironment =
await transformer.requireAndTranspileModule(config.testEnvironment);
await transformer.requireAndTranspileModule(
projectConfig.testEnvironment,
);

const environment = new Environment(config);
const environment = new Environment({
globalConfig,
projectConfig,
});
setGlobal(
environment.global as unknown as typeof globalThis,
'console',
Expand All @@ -87,7 +92,7 @@ export async function run(
setGlobal(
environment.global as unknown as typeof globalThis,
'jestProjectConfig',
config,
projectConfig,
);
setGlobal(
environment.global as unknown as typeof globalThis,
Expand All @@ -96,7 +101,7 @@ export async function run(
);

const runtime = new Runtime(
config,
projectConfig,
environment,
hasteMap.resolver,
transformer,
Expand All @@ -112,7 +117,7 @@ export async function run(
filePath,
);

for (const path of config.setupFiles) {
for (const path of projectConfig.setupFiles) {
const esm = runtime.unstable_shouldLoadAsEsm(path);

if (esm) {
Expand Down