Skip to content

feat: add jest.hoisted API for hoisting variables alongside jest.mock factories#16201

Open
ahnpnl wants to merge 1 commit into
jestjs:mainfrom
ahnpnl:feat/jest-hoisted-api
Open

feat: add jest.hoisted API for hoisting variables alongside jest.mock factories#16201
ahnpnl wants to merge 1 commit into
jestjs:mainfrom
ahnpnl:feat/jest-hoisted-api

Conversation

@ahnpnl

@ahnpnl ahnpnl commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds jest.hoisted(factory) API, mirroring vi.hoisted from Vitest

Motivation

jest.mock factories run before module imports. Today the only escape hatch for referencing variables from a jest.mock factory is the mock-prefix allowlist (any identifier matching /^mock/i). For everything else, babel-plugin-jest-hoist throws:

The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.

jest.hoisted provides an explicit, opt-in mechanism: any variable declared via jest.hoisted is hoisted to the top of the file alongside jest.mock calls, so it is available inside jest.mock factory bodies — regardless of naming.

Example

import {jest} from '@jest/globals';
import {getUser} from './user';

const {mockGet} = jest.hoisted(() => ({mockGet: jest.fn()}));

jest.mock('../api', () => ({getUser: mockGet}));

test('getUser uses hoisted mock', () => {
  mockGet.mockReturnValue({name: 'Alice'});
  expect(getUser()).toEqual({name: 'Alice'});
});

Scope

Sync overload only: jest.hoisted<T>(factory: () => T): T.

The async overload (() => Promise<T>) is intentionally not included. It would only be useful in native ESM (top-level await), but the underlying _getJestObj() template in babel-plugin-jest-hoist uses require('@jest/globals'), which is undefined under --experimental-vm-modules. This is a pre-existing limitation that also affects jest.mock and is out of scope for this PR.

Changes

  • babel-plugin-jest-hoist — recognises jest.hoisted(factory) as a hoistable call in both VariableDeclaration and ExpressionStatement positions. Supports const/let/var and destructuring. Rewrites jest.* references inside the factory body to _getJestObj().*. Throws ReferenceError for non-top-level usage, nested jest.mock/jest.hoisted inside a hoisted factory, and TypeError for non-function arguments or wrong arity.
  • jest-runtime — adds hoisted: factory => factory() to the per-file jest object built in JestGlobals.jestObjectFor, shared by both CJS and ESM module loaders.
  • jest-environment — adds hoisted<T>(factory: () => T): T to the Jest interface.
  • Docs — new section in docs/JestObjectAPI.md, README update in babel-plugin-jest-hoist, CHANGELOG.md entry.

Validation rules (vitest parity)

Case Behaviour
const x = jest.hoisted(() => 1) at top level ✅ hoisted
Bare jest.hoisted(() => { ... }) at top level ✅ hoisted
let/var / destructure ✅ hoisted
Multiple jest.hoisted calls ✅ relative order preserved
jest.hoisted inside if (true) { ... } ReferenceError
jest.hoisted inside jest.mock factory ReferenceError
jest.mock inside jest.hoisted factory ReferenceError
jest.hoisted({notAFunction: true}) TypeError
jest.hoisted() TypeError

Test plan

yarn build:js
CI= yarn jest packages/babel-plugin-jest-hoist --ci=false
# Test Suites: 1 passed, 1 total
# Tests:       52 passed, 52 total
# Snapshots:   42 passed, 42 total

yarn jest e2e/__tests__/babelPluginJestHoist
# Test Suites: 1 passed, 1 total
# Tests:       1 passed, 1 total
# (6 fixture suites: importJest, integration, integrationAutomockOff, typescript, hoisted, hoistedOrdering)

yarn lint           # ✅ no new errors
yarn lint:prettier  #

E2E fixture e2e/babel-plugin-jest-hoist/__tests__/hoistedOrdering.test.js proves hoist order by setting globalThis.__jestHoistedValue__ inside a bare jest.hoisted factory and observing it from a sibling module's import-time snapshot — mirrors Vitest's hoisted-simple.test.ts.

@netlify

netlify Bot commented May 20, 2026

Copy link
Copy Markdown

Deploy Preview for jestjs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit d018ac7
🔍 Latest deploy log https://app.netlify.com/projects/jestjs/deploys/6a0d90a3c4171d0008b5e64c
😎 Deploy Preview https://deploy-preview-16201--jestjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@ahnpnl ahnpnl force-pushed the feat/jest-hoisted-api branch from ee4558a to 70e919c Compare May 20, 2026 04:10
@github-actions github-actions Bot added the require-changelog If a PR does requires a changelog entry label May 20, 2026
@ahnpnl ahnpnl force-pushed the feat/jest-hoisted-api branch 2 times, most recently from 5f2cf1d to 0589ac6 Compare May 20, 2026 04:18
@pkg-pr-new

pkg-pr-new Bot commented May 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

babel-jest

npm i https://pkg.pr.new/babel-jest@16201

babel-plugin-jest-hoist

npm i https://pkg.pr.new/babel-plugin-jest-hoist@16201

babel-preset-jest

npm i https://pkg.pr.new/babel-preset-jest@16201

create-jest

npm i https://pkg.pr.new/create-jest@16201

@jest/diff-sequences

npm i https://pkg.pr.new/@jest/diff-sequences@16201

expect

npm i https://pkg.pr.new/expect@16201

@jest/expect-utils

npm i https://pkg.pr.new/@jest/expect-utils@16201

jest

npm i https://pkg.pr.new/jest@16201

jest-changed-files

npm i https://pkg.pr.new/jest-changed-files@16201

jest-circus

npm i https://pkg.pr.new/jest-circus@16201

jest-cli

npm i https://pkg.pr.new/jest-cli@16201

jest-config

npm i https://pkg.pr.new/jest-config@16201

@jest/console

npm i https://pkg.pr.new/@jest/console@16201

@jest/core

npm i https://pkg.pr.new/@jest/core@16201

@jest/create-cache-key-function

npm i https://pkg.pr.new/@jest/create-cache-key-function@16201

jest-diff

npm i https://pkg.pr.new/jest-diff@16201

jest-docblock

npm i https://pkg.pr.new/jest-docblock@16201

jest-each

npm i https://pkg.pr.new/jest-each@16201

@jest/environment

npm i https://pkg.pr.new/@jest/environment@16201

jest-environment-jsdom

npm i https://pkg.pr.new/jest-environment-jsdom@16201

@jest/environment-jsdom-abstract

npm i https://pkg.pr.new/@jest/environment-jsdom-abstract@16201

jest-environment-node

npm i https://pkg.pr.new/jest-environment-node@16201

@jest/expect

npm i https://pkg.pr.new/@jest/expect@16201

@jest/fake-timers

npm i https://pkg.pr.new/@jest/fake-timers@16201

@jest/get-type

npm i https://pkg.pr.new/@jest/get-type@16201

@jest/globals

npm i https://pkg.pr.new/@jest/globals@16201

jest-haste-map

npm i https://pkg.pr.new/jest-haste-map@16201

jest-jasmine2

npm i https://pkg.pr.new/jest-jasmine2@16201

jest-leak-detector

npm i https://pkg.pr.new/jest-leak-detector@16201

jest-matcher-utils

npm i https://pkg.pr.new/jest-matcher-utils@16201

jest-message-util

npm i https://pkg.pr.new/jest-message-util@16201

jest-mock

npm i https://pkg.pr.new/jest-mock@16201

@jest/pattern

npm i https://pkg.pr.new/@jest/pattern@16201

jest-phabricator

npm i https://pkg.pr.new/jest-phabricator@16201

jest-regex-util

npm i https://pkg.pr.new/jest-regex-util@16201

@jest/reporters

npm i https://pkg.pr.new/@jest/reporters@16201

jest-resolve

npm i https://pkg.pr.new/jest-resolve@16201

jest-resolve-dependencies

npm i https://pkg.pr.new/jest-resolve-dependencies@16201

jest-runner

npm i https://pkg.pr.new/jest-runner@16201

jest-runtime

npm i https://pkg.pr.new/jest-runtime@16201

@jest/schemas

npm i https://pkg.pr.new/@jest/schemas@16201

jest-snapshot

npm i https://pkg.pr.new/jest-snapshot@16201

@jest/snapshot-utils

npm i https://pkg.pr.new/@jest/snapshot-utils@16201

@jest/source-map

npm i https://pkg.pr.new/@jest/source-map@16201

@jest/test-result

npm i https://pkg.pr.new/@jest/test-result@16201

@jest/test-sequencer

npm i https://pkg.pr.new/@jest/test-sequencer@16201

@jest/transform

npm i https://pkg.pr.new/@jest/transform@16201

@jest/types

npm i https://pkg.pr.new/@jest/types@16201

jest-util

npm i https://pkg.pr.new/jest-util@16201

jest-validate

npm i https://pkg.pr.new/jest-validate@16201

jest-watcher

npm i https://pkg.pr.new/jest-watcher@16201

jest-worker

npm i https://pkg.pr.new/jest-worker@16201

pretty-format

npm i https://pkg.pr.new/pretty-format@16201

commit: d018ac7

@ahnpnl ahnpnl marked this pull request as ready for review May 20, 2026 05:36
Copilot AI review requested due to automatic review settings May 20, 2026 05:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new jest.hoisted(factory) API to support hoisting explicit variable initializers alongside jest.mock hoisting, enabling safe references from jest.mock factories without relying on the mock* naming allowlist.

Changes:

  • Extend babel-plugin-jest-hoist to recognize and hoist jest.hoisted(...) used in both declarations and bare statements, and to rewrite jest references inside hoisted factories.
  • Add jest.hoisted to the runtime-provided per-file jest object and to the Jest environment type interface.
  • Add unit + e2e coverage and document the new API (including changelog entry).

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/jest-runtime/src/internals/JestGlobals.ts Exposes hoisted on the per-file jest object implementation.
packages/jest-environment/src/index.ts Adds hoisted<T>(factory) to the Jest interface with documentation.
packages/babel-plugin-jest-hoist/src/index.ts Implements detection, validation, rewriting, and hoisting for jest.hoisted(...).
packages/babel-plugin-jest-hoist/src/tests/hoistPlugin.nodejs18.test.ts Adds transform tests covering jest.hoisted syntax/behavior and errors.
packages/babel-plugin-jest-hoist/src/tests/snapshots/hoistPlugin.nodejs20plus.test.ts.snap Updates snapshots for the new hoisting transforms (Babel 7/8).
packages/babel-plugin-jest-hoist/src/tests/snapshots/hoistPlugin.nodejs18.test.ts.snap Adds snapshots for the new hoisting transforms (Node 18 suite).
packages/babel-plugin-jest-hoist/README.md Documents that the plugin also hoists jest.hoisted calls.
e2e/babel-plugin-jest-hoist/tests/hoistedOrdering.test.js E2E fixture asserting hoist ordering relative to module import initialization.
e2e/babel-plugin-jest-hoist/tests/hoisted.test.js E2E fixture asserting jest.hoisted bindings are visible in jest.mock factories.
e2e/babel-plugin-jest-hoist/test_modules/relyOnHoisted.js Module used by the e2e ordering test to observe import-time state.
e2e/tests/babelPluginJestHoist.test.ts Updates the expected number of fixture test suites.
docs/JestObjectAPI.md Adds public API documentation for jest.hoisted(factory).
CHANGELOG.md Adds a Features entry describing the new jest.hoisted API.

Comment thread packages/babel-plugin-jest-hoist/src/index.ts Outdated
Comment thread packages/babel-plugin-jest-hoist/src/index.ts Outdated
Comment thread packages/babel-plugin-jest-hoist/src/index.ts Outdated
Comment thread packages/babel-plugin-jest-hoist/src/index.ts Outdated
Comment thread docs/JestObjectAPI.md Outdated
Comment thread docs/JestObjectAPI.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread packages/jest-environment/src/index.ts Outdated
Comment thread packages/babel-plugin-jest-hoist/src/index.ts
@ahnpnl ahnpnl force-pushed the feat/jest-hoisted-api branch from 0589ac6 to 1016558 Compare May 20, 2026 06:40
…jest.mock factories

# Conflicts:
#	CHANGELOG.md
@ahnpnl ahnpnl force-pushed the feat/jest-hoisted-api branch from 1016558 to d018ac7 Compare May 20, 2026 10:44
@ahnpnl ahnpnl changed the title feat: add jest.hoisted API for hoisting variables alongside jest.mock factories feat: add jest.hoisted API for hoisting variables alongside jest.mock factories May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

require-changelog If a PR does requires a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants