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

[Bug]: babel-jest doesn't hoist references to jest correctly with @jest/globals #15496

Open
anilanar opened this issue Feb 9, 2025 · 2 comments

Comments

@anilanar
Copy link
Contributor

anilanar commented Feb 9, 2025

Version

jest: 29.7.0

babel-preset-jest: 29.6.3

Steps to reproduce

These are some examples where hoisting works wrong:

import {jest} from '@jest/globals';


jest.mock('some-module', () => {
  const x = jest.requireActual('some-module');
  return x;
});

// ⬇️⬇️⬇️

_getJestObj().mock('some-module', () => {
  const x = jest.requireActual('some-module'); // should've been _getJestObj().requireActual
  return x;
});
function _getJestObj() {
  const {
    jest
  } = require("@jest/globals");
  _getJestObj = () => jest;
  return jest;
}
import { jest } from '@jest/globals';
import {jest} from '@jest/globals';


jest.mock('some-module', () => {
  const x = jest.requireActual('some-module');
  return x;
});

// ⬇️⬇️⬇️

_getJestObj().mock('some-module', () => {
  const x = jest.fn();  // should've been _getJestObj().fn
  return {
    x
  };
});
function _getJestObj() {
  const {
    jest
  } = require("@jest/globals");
  _getJestObj = () => jest;
  return jest;
}
import { jest } from '@jest/globals';
import {jest} from '@jest/globals';

jest.mock('some-module', () => ({
  x: jest.fn()
}));

// ⬇️⬇️⬇️

_getJestObj().mock('some-module', () => ({
  x: jest.fn() // should've been _getJestObj().fn
}));
function _getJestObj() {
  const {
    jest
  } = require("@jest/globals");
  _getJestObj = () => jest;
  return jest;
}
import { jest } from '@jest/globals';

Expected behavior

Expected correct hoisting.

Actual behavior

Wrong hoisting.

Additional context

No response

Environment

System:
    OS: macOS 15.2
    CPU: (11) arm64 Apple M3 Pro
  Binaries:
    Node: 20.11.0 - /nix/store/yxjxfg053nbr6rqzcicifsvzab8aa072-nodejs-20.11.0/bin/node
    Yarn: 4.6.0 - /nix/store/7i5rhk1svf3b0hz6j5xf1rmy75s1l2l6-yarn-1.22.21/bin/yarn
    npm: 10.2.4 - /nix/store/yxjxfg053nbr6rqzcicifsvzab8aa072-nodejs-20.11.0/bin/npm
  npmPackages:
    jest: 29.7.0 => 29.7.0
@anilanar
Copy link
Contributor Author

anilanar commented Feb 9, 2025

Is there a reason for not hoisting top-level @jest/globals imports (if they exist) to the top of the file instead of the current solution?

@anilanar
Copy link
Contributor Author

anilanar commented Feb 10, 2025

Our workaround:

  1. Opt out of babel-preset-jest:
    a. Upgrade babel-jest to v30-alpha
    b. Pass excludeJestPreset: true option. (It'd be great to backport this to v29!)
  2. Implement a babel plugin that hoists all top-level @jest/globals imports/requires to the top of the file (see below).
  3. Modify babel.config
    a. add 'babel-plugin-jest-hoist' to plugins.
    b. add newly developed plugin to plugins.

Here's the custom plugin code:

module.exports = function (babel) {
  const { types: t } = babel;

  return {
    name: 'hoist-jest-globals',
    post({ path }) {
      const jestGlobals = [];

      path.get('body').forEach(nodePath => {
        if (nodePath.isImportDeclaration() && nodePath.node.source.value === '@jest/globals') {
          jestGlobals.push(nodePath.node);
          nodePath.remove();
        } else if (nodePath.isVariableDeclaration()) {
          const declarations = nodePath.get('declarations');
          declarations.forEach(decl => {
            const init = decl.get('init');
            if (
              init.isCallExpression() &&
              init.get('callee').isIdentifier({ name: 'require' }) &&
              init.get('arguments').length === 1 &&
              init.get('arguments.0').isStringLiteral({ value: '@jest/globals' })
            ) {
              jestGlobals.push(nodePath.node);
              nodePath.remove();
            }
          });
        }
      });

      if (jestGlobals.length > 0) {
        path.unshiftContainer('body', jestGlobals);
      }
    },
  };
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant