Skip to content

Commit

Permalink
feat: add new injectGlobals config and CLI option to disable inject…
Browse files Browse the repository at this point in the history
…ing global variables into the runtime (#10484)
  • Loading branch information
SimenB authored Sep 7, 2020
1 parent c5785b9 commit 521cd09
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 21 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

- `[jest-circus, jest-config, jest-runtime]` Add new `injectGlobals` config and CLI option to disable injecting global variables into the runtime ([#10484](https://github.com/facebook/jest/pull/10484))

### Fixes

### Chore & Maintenance
Expand Down
16 changes: 16 additions & 0 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,22 @@ Show the help information, similar to this page.

Generate a basic configuration file. Based on your project, Jest will ask you a few questions that will help to generate a `jest.config.js` file with a short description for each option.

### `--injectGlobals`

Insert Jest's globals (`expect`, `test`, `describe`, `beforeEach` etc.) into the global environment. If you set this to `false`, you should import from `@jest/globals`, e.g.

```ts
import {expect, jest, test} from '@jest/globals';

jest.useFakeTimers();

test('some test', () => {
expect(Date.now()).toBe(0);
});
```

_Note: This option is only supported using `jest-circus`._

### `--json`

Prints the test results in JSON. This mode will send all other test output and user messages to stderr.
Expand Down
18 changes: 18 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,24 @@ _Note: A global teardown module configured in a project (using multi-project run

_Note: The same caveat concerning transformation of `node_modules` as for `globalSetup` applies to `globalTeardown`._

### `injectGlobals` [boolean]

Default: `true`

Insert Jest's globals (`expect`, `test`, `describe`, `beforeEach` etc.) into the global environment. If you set this to `false`, you should import from `@jest/globals`, e.g.

```ts
import {expect, jest, test} from '@jest/globals';

jest.useFakeTimers();

test('some test', () => {
expect(Date.now()).toBe(0);
});
```

_Note: This option is only supported using `jest-circus`._

### `maxConcurrency` [number]

Default: `5`
Expand Down
27 changes: 27 additions & 0 deletions e2e/__tests__/__snapshots__/injectGlobals.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`globals are undefined if passed \`false\` from CLI 1`] = `
PASS __tests__/test.js
✓ no globals injected
`;

exports[`globals are undefined if passed \`false\` from CLI 2`] = `
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites.
`;
exports[`globals are undefined if passed \`false\` from config 1`] = `
PASS __tests__/test.js
✓ no globals injected
`;
exports[`globals are undefined if passed \`false\` from config 2`] = `
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: <<REPLACED>>
Ran all test suites.
`;
1 change: 1 addition & 0 deletions e2e/__tests__/__snapshots__/showConfig.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
"computeSha1": false,
"throwOnModuleCollision": false
},
"injectGlobals": true,
"moduleDirectories": [
"node_modules"
],
Expand Down
57 changes: 57 additions & 0 deletions e2e/__tests__/injectGlobals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import * as path from 'path';
import {tmpdir} from 'os';
import {wrap} from 'jest-snapshot-serializer-raw';
import {skipSuiteOnJasmine} from '@jest/test-utils';
import {json as runJest} from '../runJest';
import {
cleanup,
createEmptyPackage,
extractSummary,
writeFiles,
} from '../Utils';

const DIR = path.resolve(tmpdir(), 'injectGlobalVariables.test');
const TEST_DIR = path.resolve(DIR, '__tests__');

skipSuiteOnJasmine();

beforeEach(() => {
cleanup(DIR);
createEmptyPackage(DIR);

const content = `
const {expect: importedExpect, test: importedTest} = require('@jest/globals');
importedTest('no globals injected', () =>{
importedExpect(typeof expect).toBe('undefined');
importedExpect(typeof test).toBe('undefined');
importedExpect(typeof jest).toBe('undefined');
importedExpect(typeof beforeEach).toBe('undefined');
});
`;

writeFiles(TEST_DIR, {'test.js': content});
});

afterAll(() => cleanup(DIR));

test.each`
configSource | args
${'CLI'} | ${['--inject-globals', 'false']}
${'config'} | ${['--config', JSON.stringify({injectGlobals: false})]}
`('globals are undefined if passed `false` from $configSource', ({args}) => {
const {json, stderr, exitCode} = runJest(DIR, args);

const {summary, rest} = extractSummary(stderr);
expect(wrap(rest)).toMatchSnapshot();
expect(wrap(summary)).toMatchSnapshot();
expect(exitCode).toBe(0);
expect(json.numPassedTests).toBe(1);
});
12 changes: 10 additions & 2 deletions packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ const jestAdapter = async (
FRAMEWORK_INITIALIZER,
);

runtime
const expect = runtime
.requireInternalModule<typeof import('./jestExpect')>(EXPECT_INITIALIZER)
.default({expand: globalConfig.expand});
.default(globalConfig);

const getPrettier = () =>
config.prettierPath ? require(config.prettierPath) : null;
Expand All @@ -52,6 +52,14 @@ const jestAdapter = async (
testPath,
});

const runtimeGlobals = {expect, ...globals};
runtime.setGlobalsForRuntime(runtimeGlobals);

// TODO: `jest-circus` might be newer than `jest-config` - remove `??` for Jest 27
if (config.injectGlobals ?? true) {
Object.assign(environment.global, runtimeGlobals);
}

if (config.timers === 'fake' || config.timers === 'legacy') {
// during setup, this cannot be null (and it's fine to explode if it is)
environment.fakeTimers!.useFakeTimers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,6 @@ export const initialize = async ({
return concurrent;
})(globalsObject.test);

const nodeGlobal = global as Global.Global;
Object.assign(nodeGlobal, globalsObject);

addEventHandler(eventHandler);

if (environment.handleTestEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import type {Config} from '@jest/types';
import expect = require('expect');

import {
Expand All @@ -15,8 +16,7 @@ import {
toThrowErrorMatchingSnapshot,
} from 'jest-snapshot';

export default (config: {expand: boolean}): void => {
global.expect = expect;
export default (config: Pick<Config.GlobalConfig, 'expand'>): typeof expect => {
expect.setState({expand: config.expand});
expect.extend({
toMatchInlineSnapshot,
Expand All @@ -26,4 +26,6 @@ export default (config: {expand: boolean}): void => {
});

expect.addSnapshotSerializer = addSerializer;

return expect;
};
4 changes: 4 additions & 0 deletions packages/jest-cli/src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ export const options = {
description: 'Generate a basic configuration file',
type: 'boolean',
},
injectGlobals: {
description: 'Should Jest inject global variables or not',
type: 'boolean',
},
json: {
default: undefined,
description:
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/Defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const defaultOptions: Config.DefaultOptions = {
computeSha1: false,
throwOnModuleCollision: false,
},
injectGlobals: true,
maxConcurrency: 5,
maxWorkers: '50%',
moduleDirectories: ['node_modules'],
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/ValidConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const initialOptions: Config.InitialOptions = {
platforms: ['ios', 'android'],
throwOnModuleCollision: false,
},
injectGlobals: true,
json: false,
lastCommit: false,
logHeapUsage: true,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ const groupOptions = (
globalTeardown: options.globalTeardown,
globals: options.globals,
haste: options.haste,
injectGlobals: options.injectGlobals,
moduleDirectories: options.moduleDirectories,
moduleFileExtensions: options.moduleFileExtensions,
moduleLoader: options.moduleLoader,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,7 @@ export default function normalize(
case 'findRelatedTests':
case 'forceCoverageMatch':
case 'forceExit':
case 'injectGlobals':
case 'lastCommit':
case 'listTests':
case 'logHeapUsage':
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-environment/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export type ModuleWrapper = (
__dirname: string,
__filename: Module['filename'],
global: Global.Global,
jest: Jest,
jest?: Jest,
...extraGlobals: Array<Global.Global[keyof Global.Global]>
) => unknown;

Expand Down
43 changes: 30 additions & 13 deletions packages/jest-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ class Runtime {
private _virtualMocks: BooleanMap;
private _moduleImplementation?: typeof nativeModule.Module;
private jestObjectCaches: Map<string, Jest>;
private jestGlobals?: JestGlobals;

constructor(
config: Config.ProjectConfig,
Expand Down Expand Up @@ -1049,6 +1050,19 @@ class Runtime {

this.jestObjectCaches.set(filename, jestObject);

const lastArgs: [Jest | undefined, ...Array<any>] = [
this._config.injectGlobals ? jestObject : undefined, // jest object
this._config.extraGlobals.map<unknown>(globalVariable => {
if (this._environment.global[globalVariable]) {
return this._environment.global[globalVariable];
}

throw new Error(
`You have requested '${globalVariable}' as a global variable, but it was not present. Please check your config or your global environment.`,
);
}),
];

try {
compiledFunction.call(
localModule.exports,
Expand All @@ -1058,16 +1072,7 @@ class Runtime {
dirname, // __dirname
filename, // __filename
this._environment.global, // global object
jestObject, // jest object
...this._config.extraGlobals.map(globalVariable => {
if (this._environment.global[globalVariable]) {
return this._environment.global[globalVariable];
}

throw new Error(
`You have requested '${globalVariable}' as a global variable, but it was not present. Please check your config or your global environment.`,
);
}),
...lastArgs.filter(notEmpty),
);
} catch (error) {
this.handleExecutionError(error, localModule);
Expand Down Expand Up @@ -1609,17 +1614,17 @@ class Runtime {
);
}

private constructInjectedModuleParameters() {
private constructInjectedModuleParameters(): Array<string> {
return [
'module',
'exports',
'require',
'__dirname',
'__filename',
'global',
'jest',
this._config.injectGlobals ? 'jest' : undefined,
...this._config.extraGlobals,
];
].filter(notEmpty);
}

private handleExecutionError(e: Error, module: InitialModule): never {
Expand Down Expand Up @@ -1686,6 +1691,10 @@ class Runtime {
}

private getGlobalsFromEnvironment(): JestGlobals {
if (this.jestGlobals) {
return {...this.jestGlobals};
}

return {
afterAll: this._environment.global.afterAll,
afterEach: this._environment.global.afterEach,
Expand Down Expand Up @@ -1714,6 +1723,10 @@ class Runtime {

return source;
}

setGlobalsForRuntime(globals: JestGlobals): void {
this.jestGlobals = globals;
}
}

function invariant(condition: unknown, message?: string): asserts condition {
Expand All @@ -1722,4 +1735,8 @@ function invariant(condition: unknown, message?: string): asserts condition {
}
}

function notEmpty<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}

export = Runtime;
4 changes: 4 additions & 0 deletions packages/jest-types/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export type DefaultOptions = {
forceCoverageMatch: Array<Glob>;
globals: ConfigGlobals;
haste: HasteConfig;
injectGlobals: boolean;
maxConcurrency: number;
maxWorkers: number | string;
moduleDirectories: Array<string>;
Expand Down Expand Up @@ -144,6 +145,7 @@ export type InitialOptions = Partial<{
globalSetup: string | null | undefined;
globalTeardown: string | null | undefined;
haste: HasteConfig;
injectGlobals: boolean;
reporters: Array<string | ReporterConfig>;
logHeapUsage: boolean;
lastCommit: boolean;
Expand Down Expand Up @@ -329,6 +331,7 @@ export type ProjectConfig = {
globalTeardown?: string;
globals: ConfigGlobals;
haste: HasteConfig;
injectGlobals: boolean;
moduleDirectories: Array<string>;
moduleFileExtensions: Array<string>;
moduleLoader?: Path;
Expand Down Expand Up @@ -399,6 +402,7 @@ export type Argv = Arguments<
globalTeardown: string | null | undefined;
haste: string;
init: boolean;
injectGlobals: boolean;
json: boolean;
lastCommit: boolean;
logHeapUsage: boolean;
Expand Down

0 comments on commit 521cd09

Please sign in to comment.