Skip to content

Commit

Permalink
chore: add babel plugin to make sure Jest is unaffected by fake Promi…
Browse files Browse the repository at this point in the history
…se implementations (#7225)
  • Loading branch information
SimenB authored Oct 24, 2018
1 parent 561b767 commit 187ca9e
Show file tree
Hide file tree
Showing 14 changed files with 83 additions and 77 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
- `[jest-test-typescript-parser]` Remove from the repository ([#7232](https://github.com/facebook/jest/pull/7232))
- `[tests]` Free tests from the dependency on value of FORCE_COLOR ([#6585](https://github.com/facebook/jest/pull/6585/files))
- `[jest-diff]` Standardize filenames ([#7238](https://github.com/facebook/jest/pull/7238))
- `[*]` Add babel plugin to make sure Jest is unaffected by fake Promise implementations ([#7225](https://github.com/facebook/jest/pull/7225))

### Performance

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* @flow
*/

import type {TestResult, Status} from 'types/TestResult';
import type {AssertionResult, TestResult, Status} from 'types/TestResult';
import type {GlobalConfig, Path, ProjectConfig} from 'types/Config';
import type {Event, TestEntry} from 'types/Circus';
import type {Event, RunResult, TestEntry} from 'types/Circus';

import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
import {formatExecError, formatResultsErrors} from 'jest-message-util';
Expand All @@ -19,12 +19,11 @@ import {
buildSnapshotResolver,
} from 'jest-snapshot';
import {addEventHandler, dispatch, ROOT_DESCRIBE_BLOCK_NAME} from '../state';
import {getTestID, getOriginalPromise} from '../utils';
import {getTestID} from '../utils';
import run from '../run';
// eslint-disable-next-line import/default
import globals from '../index';

const Promise = getOriginalPromise();
export const initialize = ({
config,
getPrettier,
Expand Down Expand Up @@ -123,46 +122,48 @@ export const runAndTransformResultsToJestFormat = async ({
globalConfig: GlobalConfig,
testPath: string,
}): Promise<TestResult> => {
const runResult = await run();
const runResult: RunResult = await run();

let numFailingTests = 0;
let numPassingTests = 0;
let numPendingTests = 0;
let numTodoTests = 0;

const assertionResults = runResult.testResults.map(testResult => {
let status: Status;
if (testResult.status === 'skip') {
status = 'pending';
numPendingTests += 1;
} else if (testResult.status === 'todo') {
status = 'todo';
numTodoTests += 1;
} else if (testResult.errors.length) {
status = 'failed';
numFailingTests += 1;
} else {
status = 'passed';
numPassingTests += 1;
}

const ancestorTitles = testResult.testPath.filter(
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
);
const title = ancestorTitles.pop();

return {
ancestorTitles,
duration: testResult.duration,
failureMessages: testResult.errors,
fullName: ancestorTitles.concat(title).join(' '),
invocations: testResult.invocations,
location: testResult.location,
numPassingAsserts: 0,
status,
title: testResult.testPath[testResult.testPath.length - 1],
};
});
const assertionResults: Array<AssertionResult> = runResult.testResults.map(
testResult => {
let status: Status;
if (testResult.status === 'skip') {
status = 'pending';
numPendingTests += 1;
} else if (testResult.status === 'todo') {
status = 'todo';
numTodoTests += 1;
} else if (testResult.errors.length) {
status = 'failed';
numFailingTests += 1;
} else {
status = 'passed';
numPassingTests += 1;
}

const ancestorTitles = testResult.testPath.filter(
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
);
const title = ancestorTitles.pop();

return {
ancestorTitles,
duration: testResult.duration,
failureMessages: testResult.errors,
fullName: ancestorTitles.concat(title).join(' '),
invocations: testResult.invocations,
location: testResult.location,
numPassingAsserts: 0,
status,
title: testResult.testPath[testResult.testPath.length - 1],
};
},
);

let failureMessage = formatResultsErrors(
assertionResults,
Expand Down
3 changes: 0 additions & 3 deletions packages/jest-circus/src/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ import {
getTestID,
invariant,
makeRunResult,
getOriginalPromise,
} from './utils';

const Promise = getOriginalPromise();

const run = async (): Promise<RunResult> => {
const {rootDescribeBlock} = getState();
dispatch({name: 'run_start'});
Expand Down
6 changes: 0 additions & 6 deletions packages/jest-circus/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ import prettyFormat from 'pretty-format';

import {getState} from './state';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test. Async functions return it implicitly.
// eslint-disable-next-line no-unused-vars
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
export const getOriginalPromise = () => Promise;

const stackUtils = new StackUtils({cwd: 'A path that does not exist'});

export const makeDescribe = (
Expand Down
5 changes: 0 additions & 5 deletions packages/jest-jasmine2/src/jasmine/Env.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ import checkIsError from '../is_error';
import assertionErrorMessage from '../assert_support';
import {ErrorWithStack} from 'jest-util';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test. Async functions return it implicitly.
// eslint-disable-next-line no-unused-vars
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

export default function(j$) {
function Env(options) {
options = options || {};
Expand Down
4 changes: 0 additions & 4 deletions packages/jest-jasmine2/src/p_cancelable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

'use strict';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

class CancelError extends Error {
constructor() {
super('Promise was canceled');
Expand Down
4 changes: 0 additions & 4 deletions packages/jest-jasmine2/src/p_timeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
* @flow
*/

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

// A specialized version of `p-timeout` that does not touch globals.
// It does not throw on timeout.
export default function pTimeout(
Expand Down
8 changes: 2 additions & 6 deletions packages/jest-jasmine2/src/queue_runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
* @flow
*/

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise: Class<Promise> =
global[Symbol.for('jest-native-promise')] || global.Promise;

import PCancelable from './p_cancelable';
import pTimeout from './p_timeout';

Expand All @@ -27,14 +22,15 @@ type Options = {
type QueueableFn = {
fn: (next: () => void) => void,
timeout?: () => number,
initError?: Error,
};

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

const mapper = ({fn, timeout, initError = new Error()}) => {
const mapper = ({fn, timeout, initError = new Error()}: QueueableFn) => {
let promise = new Promise(resolve => {
const next = function(err) {
if (err) {
Expand Down
4 changes: 0 additions & 4 deletions packages/jest-jasmine2/src/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ import type {
TestResult,
} from 'types/TestResult';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

import {formatResultsErrors} from 'jest-message-util';

type Suite = {
Expand Down
5 changes: 0 additions & 5 deletions packages/jest-jasmine2/src/tree_processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ type TreeNode = {
children?: Array<TreeNode>,
};

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test. Async functions return it implicitly.
// eslint-disable-next-line no-unused-vars
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

export default function treeProcessor(options: Options) {
const {
nodeComplete,
Expand Down
31 changes: 31 additions & 0 deletions scripts/babel-plugin-jest-native-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. 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.
*/

'use strict';

// This plugin exists to make sure that we use a `Promise` that has not been messed with by user code.
// Might consider extending this to other globals as well in the future

module.exports = ({template}) => {
const promiseDeclaration = template(`
var Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
`);

return {
name: 'jest-native-promise',
visitor: {
ReferencedIdentifier(path, state) {
if (path.node.name === 'Promise' && !state.injectedPromise) {
state.injectedPromise = true;
path
.findParent(p => p.isProgram())
.unshiftContainer('body', promiseDeclaration());
}
},
},
};
};
8 changes: 7 additions & 1 deletion scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ function buildFile(file, silent) {
const options = Object.assign({}, transformOptions);
options.plugins = options.plugins.slice();

if (!INLINE_REQUIRE_BLACKLIST.test(file)) {
if (INLINE_REQUIRE_BLACKLIST.test(file)) {
// The modules in the blacklist are injected into the user's sandbox
// We need to guard `Promise` there.
options.plugins.push(
require.resolve('./babel-plugin-jest-native-promise')
);
} else {
// Remove normal plugin.
options.plugins = options.plugins.filter(
plugin =>
Expand Down
3 changes: 2 additions & 1 deletion types/Circus.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

export type DoneFn = (reason?: string | Error) => void;
export type BlockFn = () => void;
export type BlockName = string | Function;
export type BlockName = string;
export type BlockMode = void | 'skip' | 'only' | 'todo';
export type TestMode = BlockMode;
export type TestName = string;
Expand Down Expand Up @@ -153,6 +153,7 @@ export type TestStatus = 'skip' | 'done' | 'todo';
export type TestResult = {|
duration: ?number,
errors: Array<FormattedError>,
invocations: number,
status: TestStatus,
location: ?{|column: number, line: number|},
testPath: Array<TestName | BlockName>,
Expand Down
1 change: 1 addition & 0 deletions types/TestResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export type AssertionResult = {|
duration?: ?Milliseconds,
failureMessages: Array<string>,
fullName: string,
invocations?: number,
location: ?Callsite,
numPassingAsserts: number,
status: Status,
Expand Down

0 comments on commit 187ca9e

Please sign in to comment.