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

fix: Preserve order of jest calls when hoisting #10536

Merged
merged 12 commits into from Oct 29, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@

### Fixes

- `[babel-plugin-jest-hoist]` Preserve order of hoisted mock nodes within containing block ([#10536](https://github.com/facebook/jest/pull/10536))
- `[jest-config]` Fix bug introduced in watch mode by PR[#10678](https://github.com/facebook/jest/pull/10678/files#r511037803) ([#10692](https://github.com/facebook/jest/pull/10692))
- `[expect]` Stop modifying the sample in `expect.objectContaining()` ([#10711](https://github.com/facebook/jest/pull/10711))
- `[jest-circus, jest-jasmine2]` fix: don't assume `stack` is always a string ([#10697](https://github.com/facebook/jest/pull/10697))
Expand Down
8 changes: 8 additions & 0 deletions e2e/babel-plugin-jest-hoist/__test_modules__/g.js
@@ -0,0 +1,8 @@
/**
* 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.
*/

export default () => 'unmocked';
2 changes: 1 addition & 1 deletion e2e/babel-plugin-jest-hoist/__tests__/integration.test.js
Expand Up @@ -75,7 +75,7 @@ myObject.mock('apple', 27);

// Variable names prefixed with `mock` (ignore case) should not throw as out-of-scope
const MockMethods = () => {};
jest.mock('../__test_modules__/f', () => MockMethods);
jest.mock('../__test_modules__/g', () => MockMethods);

describe('babel-plugin-jest-hoist', () => {
it('does not throw during transform', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/babel-plugin-jest-hoist/package.json
Expand Up @@ -20,7 +20,8 @@
},
"devDependencies": {
"@types/babel__template": "^7.0.2",
"@types/node": "*"
"@types/node": "*",
"babel-plugin-tester": "9.2.0"
},
"publishConfig": {
"access": "public"
Expand Down
@@ -0,0 +1,76 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`babel-plugin-jest-hoist top level mocking: top level mocking 1`] = `

require('x');

jest.enableAutomock();
jest.disableAutomock();

↓ ↓ ↓ ↓ ↓ ↓

_getJestObj().enableAutomock();

_getJestObj().disableAutomock();

function _getJestObj() {
const { jest } = require("@jest/globals");

_getJestObj = () => jest;

return jest;
}

require("x");


`;

exports[`babel-plugin-jest-hoist within a block with no siblings: within a block with no siblings 1`] = `

beforeEach(() => {
jest.mock('someNode')
})

↓ ↓ ↓ ↓ ↓ ↓

function _getJestObj() {
const { jest } = require("@jest/globals");

_getJestObj = () => jest;

return jest;
}

beforeEach(() => {
_getJestObj().mock("someNode");
});


`;

exports[`babel-plugin-jest-hoist within a block: within a block 1`] = `

beforeEach(() => {
require('x')
jest.mock('someNode')
})

↓ ↓ ↓ ↓ ↓ ↓

function _getJestObj() {
const { jest } = require("@jest/globals");

_getJestObj = () => jest;

return jest;
}

beforeEach(() => {
_getJestObj().mock("someNode");

require("x");
});


`;
43 changes: 43 additions & 0 deletions packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts
@@ -0,0 +1,43 @@
/**
* 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 pluginTester from 'babel-plugin-tester';
import babelPluginJestHoist from '..';

pluginTester({
plugin: babelPluginJestHoist,
pluginName: 'babel-plugin-jest-hoist',
tests: {
'top level mocking': {
code: `
require('x');

jest.enableAutomock();
jest.disableAutomock();
`,
snapshot: true,
},
'within a block': {
code: `
beforeEach(() => {
require('x')
jest.mock('someNode')
})
`,
snapshot: true,
},
'within a block with no siblings': {
code: `
beforeEach(() => {
jest.mock('someNode')
})
`,
snapshot: true,
},
},
});
34 changes: 28 additions & 6 deletions packages/babel-plugin-jest-hoist/src/index.ts
Expand Up @@ -8,11 +8,14 @@

import type {NodePath} from '@babel/traverse';
import {
BlockStatement,
CallExpression,
Expression,
Identifier,
Node,
Program,
callExpression,
emptyStatement,
isIdentifier,
} from '@babel/types';
import {statement} from '@babel/template';
Expand Down Expand Up @@ -113,7 +116,7 @@ FUNCTIONS.mock = args => {

const ids: Set<NodePath<Identifier>> = new Set();
const parentScope = moduleFactory.parentPath.scope;
// @ts-expect-error: ReferencedIdentifier is not known on visitors
// @ts-expect-error: ReferencedIdentifier and blacklist are not known on visitors
moduleFactory.traverse(IDVisitor, {ids});
for (const id of ids) {
const {name} = id.node;
Expand Down Expand Up @@ -281,14 +284,33 @@ export default (): PluginObj<{
},
// in `post` to make sure we come after an import transform and can unshift above the `require`s
post({path: program}: {path: NodePath<Program>}) {
const self = this;
visitBlock(program);
program.traverse({
CallExpression: callExpr => {
BlockStatement: visitBlock,
});

function visitBlock(block: NodePath<BlockStatement> | NodePath<Program>) {
// use a temporary empty statement instead of the real first statement, which may itself be hoisted
const [firstNonHoistedStatementOfBlock] = block.unshiftContainer(
'body',
emptyStatement(),
);
block.traverse({
CallExpression: visitCallExpr,
// do not traverse into nested blocks, or we'll hoist calls in there out to this block
// @ts-expect-error blacklist is not known
blacklist: ['BlockStatement'],
});
firstNonHoistedStatementOfBlock.remove();

function visitCallExpr(callExpr: NodePath<CallExpression>) {
const {
node: {callee},
} = callExpr;
if (
isIdentifier(callee) &&
callee.name === this.jestObjGetterIdentifier?.name
callee.name === self.jestObjGetterIdentifier?.name
) {
const mockStmt = callExpr.getStatementParent();

Expand All @@ -297,12 +319,12 @@ export default (): PluginObj<{
const mockStmtParent = mockStmt.parentPath;
if (mockStmtParent.isBlock()) {
mockStmt.remove();
mockStmtParent.unshiftContainer('body', [mockStmtNode]);
firstNonHoistedStatementOfBlock.insertBefore(mockStmtNode);
}
}
}
},
});
}
}
},
});
/* eslint-enable */
43 changes: 41 additions & 2 deletions yarn.lock
Expand Up @@ -3251,6 +3251,16 @@ __metadata:
languageName: node
linkType: hard

"@types/babel-plugin-tester@npm:^9.0.0":
version: 9.0.0
resolution: "@types/babel-plugin-tester@npm:9.0.0"
dependencies:
"@types/babel__core": "*"
"@types/prettier": "*"
checksum: d6b465ef6ce980927dd3b10ae0a39fd8384688ba2d15bf2a30ac4ae567572abb2768992e7c2389f873c87c8d9ba5eafe88feb399e1e9e93f3cbfc5fe27da7077
languageName: node
linkType: hard

"@types/babel-types@npm:*":
version: 7.0.9
resolution: "@types/babel-types@npm:7.0.9"
Expand All @@ -3265,7 +3275,7 @@ __metadata:
languageName: node
linkType: hard

"@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.0.4, @types/babel__core@npm:^7.1.0, @types/babel__core@npm:^7.1.7":
"@types/babel__core@npm:*, @types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.0.4, @types/babel__core@npm:^7.1.0, @types/babel__core@npm:^7.1.7":
version: 7.1.7
resolution: "@types/babel__core@npm:7.1.7"
dependencies:
Expand Down Expand Up @@ -3638,6 +3648,13 @@ __metadata:
languageName: node
linkType: hard

"@types/prettier@npm:*":
version: 2.1.1
resolution: "@types/prettier@npm:2.1.1"
checksum: 3671bedc845a0e61bb8eb698746e1f6d1201ac784f95c536cd653c1406a51c0e9c338ecbbc73f1b5fd5fe0b0af98edf7e85428810357d959355ab46b3a63ebe6
languageName: node
linkType: hard

"@types/prettier@npm:^2.0.0":
version: 2.1.5
resolution: "@types/prettier@npm:2.1.5"
Expand Down Expand Up @@ -4813,6 +4830,7 @@ __metadata:
"@types/babel__template": ^7.0.2
"@types/babel__traverse": ^7.0.6
"@types/node": "*"
babel-plugin-tester: 9.2.0
languageName: unknown
linkType: soft

Expand All @@ -4830,6 +4848,20 @@ __metadata:
languageName: node
linkType: hard

"babel-plugin-tester@npm:9.2.0":
version: 9.2.0
resolution: "babel-plugin-tester@npm:9.2.0"
dependencies:
"@types/babel-plugin-tester": ^9.0.0
lodash.mergewith: ^4.6.2
prettier: ^2.0.1
strip-indent: ^3.0.0
peerDependencies:
"@babel/core": ^7.9.0
checksum: ce247d30010fc4e7f28733187a6b347f292226078a72c58c399d2f30f5ec2a054cdfe81831d3455292a58ab3db3df35cae1db8a44234a35466090b90075a619c
languageName: node
linkType: hard

"babel-plugin-transform-typescript-metadata@npm:*":
version: 0.3.1
resolution: "babel-plugin-transform-typescript-metadata@npm:0.3.1"
Expand Down Expand Up @@ -12841,6 +12873,13 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"lodash.mergewith@npm:^4.6.2":
version: 4.6.2
resolution: "lodash.mergewith@npm:4.6.2"
checksum: 3561b63cebc629721ab4c016627fc54929ee33cdef1854b4a15ade71dd8eb5f2fc602830efe5395aed41c607d65e2cce356667116aa7156b82468594b42ab95f
languageName: node
linkType: hard

"lodash.padstart@npm:^4.6.1":
version: 4.6.1
resolution: "lodash.padstart@npm:4.6.1"
Expand Down Expand Up @@ -15865,7 +15904,7 @@ fsevents@^1.2.7:
languageName: node
linkType: hard

"prettier@npm:^2.1.1":
"prettier@npm:^2.0.1, prettier@npm:^2.1.1":
version: 2.1.2
resolution: "prettier@npm:2.1.2"
bin:
Expand Down