Skip to content

Commit 187ca9e

Browse files
authored
chore: add babel plugin to make sure Jest is unaffected by fake Promise implementations (jestjs#7225)
1 parent 561b767 commit 187ca9e

File tree

14 files changed

+83
-77
lines changed

14 files changed

+83
-77
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
- `[jest-test-typescript-parser]` Remove from the repository ([#7232](https://github.com/facebook/jest/pull/7232))
8181
- `[tests]` Free tests from the dependency on value of FORCE_COLOR ([#6585](https://github.com/facebook/jest/pull/6585/files))
8282
- `[jest-diff]` Standardize filenames ([#7238](https://github.com/facebook/jest/pull/7238))
83+
- `[*]` Add babel plugin to make sure Jest is unaffected by fake Promise implementations ([#7225](https://github.com/facebook/jest/pull/7225))
8384

8485
### Performance
8586

packages/jest-circus/src/legacy_code_todo_rewrite/jest_adapter_init.js

+39-38
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* @flow
88
*/
99

10-
import type {TestResult, Status} from 'types/TestResult';
10+
import type {AssertionResult, TestResult, Status} from 'types/TestResult';
1111
import type {GlobalConfig, Path, ProjectConfig} from 'types/Config';
12-
import type {Event, TestEntry} from 'types/Circus';
12+
import type {Event, RunResult, TestEntry} from 'types/Circus';
1313

1414
import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
1515
import {formatExecError, formatResultsErrors} from 'jest-message-util';
@@ -19,12 +19,11 @@ import {
1919
buildSnapshotResolver,
2020
} from 'jest-snapshot';
2121
import {addEventHandler, dispatch, ROOT_DESCRIBE_BLOCK_NAME} from '../state';
22-
import {getTestID, getOriginalPromise} from '../utils';
22+
import {getTestID} from '../utils';
2323
import run from '../run';
2424
// eslint-disable-next-line import/default
2525
import globals from '../index';
2626

27-
const Promise = getOriginalPromise();
2827
export const initialize = ({
2928
config,
3029
getPrettier,
@@ -123,46 +122,48 @@ export const runAndTransformResultsToJestFormat = async ({
123122
globalConfig: GlobalConfig,
124123
testPath: string,
125124
}): Promise<TestResult> => {
126-
const runResult = await run();
125+
const runResult: RunResult = await run();
127126

128127
let numFailingTests = 0;
129128
let numPassingTests = 0;
130129
let numPendingTests = 0;
131130
let numTodoTests = 0;
132131

133-
const assertionResults = runResult.testResults.map(testResult => {
134-
let status: Status;
135-
if (testResult.status === 'skip') {
136-
status = 'pending';
137-
numPendingTests += 1;
138-
} else if (testResult.status === 'todo') {
139-
status = 'todo';
140-
numTodoTests += 1;
141-
} else if (testResult.errors.length) {
142-
status = 'failed';
143-
numFailingTests += 1;
144-
} else {
145-
status = 'passed';
146-
numPassingTests += 1;
147-
}
148-
149-
const ancestorTitles = testResult.testPath.filter(
150-
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
151-
);
152-
const title = ancestorTitles.pop();
153-
154-
return {
155-
ancestorTitles,
156-
duration: testResult.duration,
157-
failureMessages: testResult.errors,
158-
fullName: ancestorTitles.concat(title).join(' '),
159-
invocations: testResult.invocations,
160-
location: testResult.location,
161-
numPassingAsserts: 0,
162-
status,
163-
title: testResult.testPath[testResult.testPath.length - 1],
164-
};
165-
});
132+
const assertionResults: Array<AssertionResult> = runResult.testResults.map(
133+
testResult => {
134+
let status: Status;
135+
if (testResult.status === 'skip') {
136+
status = 'pending';
137+
numPendingTests += 1;
138+
} else if (testResult.status === 'todo') {
139+
status = 'todo';
140+
numTodoTests += 1;
141+
} else if (testResult.errors.length) {
142+
status = 'failed';
143+
numFailingTests += 1;
144+
} else {
145+
status = 'passed';
146+
numPassingTests += 1;
147+
}
148+
149+
const ancestorTitles = testResult.testPath.filter(
150+
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
151+
);
152+
const title = ancestorTitles.pop();
153+
154+
return {
155+
ancestorTitles,
156+
duration: testResult.duration,
157+
failureMessages: testResult.errors,
158+
fullName: ancestorTitles.concat(title).join(' '),
159+
invocations: testResult.invocations,
160+
location: testResult.location,
161+
numPassingAsserts: 0,
162+
status,
163+
title: testResult.testPath[testResult.testPath.length - 1],
164+
};
165+
},
166+
);
166167

167168
let failureMessage = formatResultsErrors(
168169
assertionResults,

packages/jest-circus/src/run.js

-3
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,8 @@ import {
2323
getTestID,
2424
invariant,
2525
makeRunResult,
26-
getOriginalPromise,
2726
} from './utils';
2827

29-
const Promise = getOriginalPromise();
30-
3128
const run = async (): Promise<RunResult> => {
3229
const {rootDescribeBlock} = getState();
3330
dispatch({name: 'run_start'});

packages/jest-circus/src/utils.js

-6
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ import prettyFormat from 'pretty-format';
3232

3333
import {getState} from './state';
3434

35-
// Try getting the real promise object from the context, if available. Someone
36-
// could have overridden it in a test. Async functions return it implicitly.
37-
// eslint-disable-next-line no-unused-vars
38-
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
39-
export const getOriginalPromise = () => Promise;
40-
4135
const stackUtils = new StackUtils({cwd: 'A path that does not exist'});
4236

4337
export const makeDescribe = (

packages/jest-jasmine2/src/jasmine/Env.js

-5
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ import checkIsError from '../is_error';
3737
import assertionErrorMessage from '../assert_support';
3838
import {ErrorWithStack} from 'jest-util';
3939

40-
// Try getting the real promise object from the context, if available. Someone
41-
// could have overridden it in a test. Async functions return it implicitly.
42-
// eslint-disable-next-line no-unused-vars
43-
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
44-
4540
export default function(j$) {
4641
function Env(options) {
4742
options = options || {};

packages/jest-jasmine2/src/p_cancelable.js

-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
'use strict';
44

5-
// Try getting the real promise object from the context, if available. Someone
6-
// could have overridden it in a test.
7-
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
8-
95
class CancelError extends Error {
106
constructor() {
117
super('Promise was canceled');

packages/jest-jasmine2/src/p_timeout.js

-4
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
* @flow
88
*/
99

10-
// Try getting the real promise object from the context, if available. Someone
11-
// could have overridden it in a test.
12-
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
13-
1410
// A specialized version of `p-timeout` that does not touch globals.
1511
// It does not throw on timeout.
1612
export default function pTimeout(

packages/jest-jasmine2/src/queue_runner.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77
* @flow
88
*/
99

10-
// Try getting the real promise object from the context, if available. Someone
11-
// could have overridden it in a test.
12-
const Promise: Class<Promise> =
13-
global[Symbol.for('jest-native-promise')] || global.Promise;
14-
1510
import PCancelable from './p_cancelable';
1611
import pTimeout from './p_timeout';
1712

@@ -27,14 +22,15 @@ type Options = {
2722
type QueueableFn = {
2823
fn: (next: () => void) => void,
2924
timeout?: () => number,
25+
initError?: Error,
3026
};
3127

3228
export default function queueRunner(options: Options) {
3329
const token = new PCancelable((onCancel, resolve) => {
3430
onCancel(resolve);
3531
});
3632

37-
const mapper = ({fn, timeout, initError = new Error()}) => {
33+
const mapper = ({fn, timeout, initError = new Error()}: QueueableFn) => {
3834
let promise = new Promise(resolve => {
3935
const next = function(err) {
4036
if (err) {

packages/jest-jasmine2/src/reporter.js

-4
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ import type {
1616
TestResult,
1717
} from 'types/TestResult';
1818

19-
// Try getting the real promise object from the context, if available. Someone
20-
// could have overridden it in a test.
21-
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
22-
2319
import {formatResultsErrors} from 'jest-message-util';
2420

2521
type Suite = {

packages/jest-jasmine2/src/tree_processor.js

-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ type TreeNode = {
2626
children?: Array<TreeNode>,
2727
};
2828

29-
// Try getting the real promise object from the context, if available. Someone
30-
// could have overridden it in a test. Async functions return it implicitly.
31-
// eslint-disable-next-line no-unused-vars
32-
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
33-
3429
export default function treeProcessor(options: Options) {
3530
const {
3631
nodeComplete,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
// This plugin exists to make sure that we use a `Promise` that has not been messed with by user code.
11+
// Might consider extending this to other globals as well in the future
12+
13+
module.exports = ({template}) => {
14+
const promiseDeclaration = template(`
15+
var Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
16+
`);
17+
18+
return {
19+
name: 'jest-native-promise',
20+
visitor: {
21+
ReferencedIdentifier(path, state) {
22+
if (path.node.name === 'Promise' && !state.injectedPromise) {
23+
state.injectedPromise = true;
24+
path
25+
.findParent(p => p.isProgram())
26+
.unshiftContainer('body', promiseDeclaration());
27+
}
28+
},
29+
},
30+
};
31+
};

scripts/build.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,13 @@ function buildFile(file, silent) {
150150
const options = Object.assign({}, transformOptions);
151151
options.plugins = options.plugins.slice();
152152

153-
if (!INLINE_REQUIRE_BLACKLIST.test(file)) {
153+
if (INLINE_REQUIRE_BLACKLIST.test(file)) {
154+
// The modules in the blacklist are injected into the user's sandbox
155+
// We need to guard `Promise` there.
156+
options.plugins.push(
157+
require.resolve('./babel-plugin-jest-native-promise')
158+
);
159+
} else {
154160
// Remove normal plugin.
155161
options.plugins = options.plugins.filter(
156162
plugin =>

types/Circus.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
export type DoneFn = (reason?: string | Error) => void;
1111
export type BlockFn = () => void;
12-
export type BlockName = string | Function;
12+
export type BlockName = string;
1313
export type BlockMode = void | 'skip' | 'only' | 'todo';
1414
export type TestMode = BlockMode;
1515
export type TestName = string;
@@ -153,6 +153,7 @@ export type TestStatus = 'skip' | 'done' | 'todo';
153153
export type TestResult = {|
154154
duration: ?number,
155155
errors: Array<FormattedError>,
156+
invocations: number,
156157
status: TestStatus,
157158
location: ?{|column: number, line: number|},
158159
testPath: Array<TestName | BlockName>,

types/TestResult.js

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export type AssertionResult = {|
9898
duration?: ?Milliseconds,
9999
failureMessages: Array<string>,
100100
fullName: string,
101+
invocations?: number,
101102
location: ?Callsite,
102103
numPassingAsserts: number,
103104
status: Status,

0 commit comments

Comments
 (0)