From bf9d74c98be18d8fcf4519b112669da44c17f4c3 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sun, 28 Aug 2022 23:48:52 +0200 Subject: [PATCH 1/6] failing test --- .../src/__tests__/hoistPlugin.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts b/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts index c6ad87de8107..1832acf8bb1a 100644 --- a/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts +++ b/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts @@ -90,6 +90,17 @@ pluginTester({ formatResult, snapshot: true, }, + 'imported jest within jest': { + code: formatResult(` + import {jest} from '@jest/globals'; + + jest.mock('some-module', () => { + jest.mock('some-module'); + }); + `), + formatResult, + snapshot: true, + }, }, /* eslint-enable */ }); From b80915c0df0133faaa0a07bbc05c95e38c4c741b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 29 Aug 2022 00:21:03 +0200 Subject: [PATCH 2/6] Fix `jest.mock` in `jest.mock` --- .../__snapshots__/hoistPlugin.test.ts.snap | 50 +++++++++++++++++++ .../src/__tests__/hoistPlugin.test.ts | 11 +++- packages/babel-plugin-jest-hoist/src/index.ts | 13 +++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap b/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap index 584845c2dda4..d483c35dc939 100644 --- a/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap +++ b/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap @@ -38,6 +38,56 @@ function _getJestObj() { } +`; + +exports[`babel-plugin-jest-hoist global jest.mock within jest: global jest.mock within jest 1`] = ` + +jest.mock('some-module', () => { + jest.mock('some-module'); +}); + + ↓ ↓ ↓ ↓ ↓ ↓ + +_getJestObj().mock('some-module', () => { + _getJestObj().mock('some-module'); +}); + +function _getJestObj() { + const {jest} = require('@jest/globals'); + + _getJestObj = () => jest; + + return jest; +} + + +`; + +exports[`babel-plugin-jest-hoist imported jest.mock within jest: imported jest.mock within jest 1`] = ` + +import {jest} from '@jest/globals'; + +jest.mock('some-module', () => { + jest.mock('some-module'); +}); + + ↓ ↓ ↓ ↓ ↓ ↓ + +_getJestObj().mock('some-module', () => { + _getJestObj().mock('some-module'); +}); + +function _getJestObj() { + const {jest} = require('@jest/globals'); + + _getJestObj = () => jest; + + return jest; +} + +import {jest} from '@jest/globals'; + + `; exports[`babel-plugin-jest-hoist required \`jest\` within \`jest\`: required \`jest\` within \`jest\` 1`] = ` diff --git a/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts b/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts index 1832acf8bb1a..617a6e47834e 100644 --- a/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts +++ b/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts @@ -90,7 +90,7 @@ pluginTester({ formatResult, snapshot: true, }, - 'imported jest within jest': { + 'imported jest.mock within jest': { code: formatResult(` import {jest} from '@jest/globals'; @@ -101,6 +101,15 @@ pluginTester({ formatResult, snapshot: true, }, + 'global jest.mock within jest': { + code: formatResult(` + jest.mock('some-module', () => { + jest.mock('some-module'); + }); + `), + formatResult, + snapshot: true, + }, }, /* eslint-enable */ }); diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 2218dcec5421..fb3aefc59e89 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -161,6 +161,19 @@ FUNCTIONS.mock = args => { hoistedVariables.add(node); isAllowedIdentifier = true; } + } else if (binding?.path.isImportSpecifier()) { + const importDecl = binding.path + .parentPath as NodePath; + const imported = binding.path.node.imported; + if ( + importDecl.node.source.value === JEST_GLOBALS_MODULE_NAME && + (isIdentifier(imported) ? imported.name : imported.value) === + JEST_GLOBALS_MODULE_JEST_EXPORT_NAME + ) { + isAllowedIdentifier = true; + // Imports are already hoisted, so we don't need to add it + // to hoistedVariables. + } } } From b7048eebf2f6ad6df5ca3e568554d5a91b81f93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Mon, 29 Aug 2022 00:30:23 +0200 Subject: [PATCH 3/6] Transform all `jest` refs in hoisted code --- .../__snapshots__/hoistPlugin.test.ts.snap | 61 ++++++++++++++++++- .../src/__tests__/hoistPlugin.test.ts | 28 ++++++++- packages/babel-plugin-jest-hoist/src/index.ts | 23 ++++++- 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap b/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap index d483c35dc939..3a5a0817baed 100644 --- a/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap +++ b/packages/babel-plugin-jest-hoist/src/__tests__/__snapshots__/hoistPlugin.test.ts.snap @@ -40,7 +40,7 @@ function _getJestObj() { `; -exports[`babel-plugin-jest-hoist global jest.mock within jest: global jest.mock within jest 1`] = ` +exports[`babel-plugin-jest-hoist global jest.mock within jest.mock: global jest.mock within jest.mock 1`] = ` jest.mock('some-module', () => { jest.mock('some-module'); @@ -63,7 +63,34 @@ function _getJestObj() { `; -exports[`babel-plugin-jest-hoist imported jest.mock within jest: imported jest.mock within jest 1`] = ` +exports[`babel-plugin-jest-hoist global jest.requireActual in jest.mock: global jest.requireActual in jest.mock 1`] = ` + +jest.mock('some-module', () => { + jest.requireActual('some-module'); +}); + +jest.requireActual('some-module'); + + ↓ ↓ ↓ ↓ ↓ ↓ + +_getJestObj().mock('some-module', () => { + _getJestObj().requireActual('some-module'); +}); + +function _getJestObj() { + const {jest} = require('@jest/globals'); + + _getJestObj = () => jest; + + return jest; +} + +jest.requireActual('some-module'); + + +`; + +exports[`babel-plugin-jest-hoist imported jest.mock within jest.mock: imported jest.mock within jest.mock 1`] = ` import {jest} from '@jest/globals'; @@ -88,6 +115,36 @@ function _getJestObj() { import {jest} from '@jest/globals'; +`; + +exports[`babel-plugin-jest-hoist imported jest.requireActual in jest.mock: imported jest.requireActual in jest.mock 1`] = ` + +import {jest} from '@jest/globals'; + +jest.mock('some-module', () => { + jest.requireActual('some-module'); +}); + +jest.requireActual('some-module'); + + ↓ ↓ ↓ ↓ ↓ ↓ + +_getJestObj().mock('some-module', () => { + _getJestObj().requireActual('some-module'); +}); + +function _getJestObj() { + const {jest} = require('@jest/globals'); + + _getJestObj = () => jest; + + return jest; +} + +import {jest} from '@jest/globals'; +jest.requireActual('some-module'); + + `; exports[`babel-plugin-jest-hoist required \`jest\` within \`jest\`: required \`jest\` within \`jest\` 1`] = ` diff --git a/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts b/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts index 617a6e47834e..685ab680aa75 100644 --- a/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts +++ b/packages/babel-plugin-jest-hoist/src/__tests__/hoistPlugin.test.ts @@ -90,7 +90,7 @@ pluginTester({ formatResult, snapshot: true, }, - 'imported jest.mock within jest': { + 'imported jest.mock within jest.mock': { code: formatResult(` import {jest} from '@jest/globals'; @@ -101,7 +101,7 @@ pluginTester({ formatResult, snapshot: true, }, - 'global jest.mock within jest': { + 'global jest.mock within jest.mock': { code: formatResult(` jest.mock('some-module', () => { jest.mock('some-module'); @@ -110,6 +110,30 @@ pluginTester({ formatResult, snapshot: true, }, + 'imported jest.requireActual in jest.mock': { + code: formatResult(` + import {jest} from '@jest/globals'; + + jest.mock('some-module', () => { + jest.requireActual('some-module'); + }); + + jest.requireActual('some-module'); + `), + formatResult, + snapshot: true, + }, + 'global jest.requireActual in jest.mock': { + code: formatResult(` + jest.mock('some-module', () => { + jest.requireActual('some-module'); + }); + + jest.requireActual('some-module'); + `), + formatResult, + snapshot: true, + }, }, /* eslint-enable */ }); diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index fb3aefc59e89..21af10f6c2a2 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -14,6 +14,7 @@ import { CallExpression, Expression, Identifier, + ImportDeclaration, MemberExpression, Node, Program, @@ -31,6 +32,7 @@ const JEST_GLOBALS_MODULE_NAME = '@jest/globals'; const JEST_GLOBALS_MODULE_JEST_EXPORT_NAME = 'jest'; const hoistedVariables = new WeakSet(); +const hoistedJestExpressions = new WeakSet(); // We allow `jest`, `expect`, `require`, all default Node.js globals and all // ES2015 built-ins to be used inside of a `jest.mock` factory. @@ -277,9 +279,26 @@ const extractJestObjExprIfHoistable = ( // Important: Call the function check last // It might throw an error to display to the user, // which should only happen if we're already sure it's a call on the Jest object. - const functionLooksHoistable = FUNCTIONS[propertyName]?.(args); + let functionLooksHoistableOrInHoistable = FUNCTIONS[propertyName]?.(args); - return functionLooksHoistable ? jestObjExpr : null; + for ( + let path: NodePath | null = expr; + path && !functionLooksHoistableOrInHoistable; + path = path.parentPath + ) { + functionLooksHoistableOrInHoistable = hoistedJestExpressions.has( + // @ts-expect-error: it's ok if path.node is not an Expression, .has will + // just return false. + path.node, + ); + } + + if (functionLooksHoistableOrInHoistable) { + hoistedJestExpressions.add(expr.node); + return jestObjExpr; + } + + return null; }; /* eslint-disable sort-keys */ From 39c49b320c8d15319a76667882400fa43f4c69ed Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 30 Aug 2022 11:50:33 +0200 Subject: [PATCH 4/6] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3d304c7eb06..dd532aea4214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Fixes +- `[]` Expose `TransformFactory` type ([#13188](https://github.com/facebook/jest/pull/13188 + ### Chore & Maintenance ### Performance @@ -21,10 +23,10 @@ ### Features - `[expect]` [**BREAKING**] Differentiate between `MatcherContext` `MatcherUtils` and `MatcherState` types ([#13141](https://github.com/facebook/jest/pull/13141)) -- `[jest-circus]` Add support for `test.failing.each` ([#13142](https://github.com/facebook/jest/pull/13142)) +- `[jest-circus]` Add supprt for `test.failing.each` ([#13142](https://github.com/facebook/jest/pull/13142)) - `[jest-config]` [**BREAKING**] Make `snapshotFormat` default to `escapeString: false` and `printBasicPrototype: false` ([#13036](https://github.com/facebook/jest/pull/13036)) - `[jest-config]` [**BREAKING**] Remove undocumented `collectCoverageOnlyFrom` option ([#13156](https://github.com/facebook/jest/pull/13156)) -- `[jest-environment-jsdom]` [**BREAKING**] Upgrade to `jsdom@20` ([#13037](https://github.com/facebook/jest/pull/13037), [#13058](https://github.com/facebook/jest/pull/13058)) +- `[jest-environment-jsdom]` **BREAKING**] Upgrade to `jsdom@20` ([#13037](https://github.com/facebook/jest/pull/13037), [#13058](https://github.com/facebook/jest/pull/13058)) - `[@jest/globals]` Add `jest.Mocked`, `jest.MockedClass`, `jest.MockedFunction` and `jest.MockedObject` utility types ([#12727](https://github.com/facebook/jest/pull/12727)) - `[jest-mock]` [**BREAKING**] Refactor `Mocked*` utility types. `MaybeMockedDeep` and `MaybeMocked` became `Mocked` and `MockedShallow` respectively; only deep mocked variants of `MockedClass`, `MockedFunction` and `MockedObject` are exported ([#13123](https://github.com/facebook/jest/pull/13123), [#13124](https://github.com/facebook/jest/pull/13124)) - `[jest-mock]` [**BREAKING**] Change the default `jest.mocked` helper’s behavior to deep mocked ([#13125](https://github.com/facebook/jest/pull/13125)) From d2860b29f43e5c9cd99e5c885cd2650e74044ead Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 30 Aug 2022 11:53:16 +0200 Subject: [PATCH 5/6] Apply suggestions from code review --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd532aea4214..9299c4babc18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Fixes -- `[]` Expose `TransformFactory` type ([#13188](https://github.com/facebook/jest/pull/13188 +- `[babel-plugin-jest-hoist]` Support imported `jest` in mock factory ([#13188](https://github.com/facebook/jest/pull/13188 ### Chore & Maintenance @@ -23,10 +23,10 @@ ### Features - `[expect]` [**BREAKING**] Differentiate between `MatcherContext` `MatcherUtils` and `MatcherState` types ([#13141](https://github.com/facebook/jest/pull/13141)) -- `[jest-circus]` Add supprt for `test.failing.each` ([#13142](https://github.com/facebook/jest/pull/13142)) +- `[jest-circus]` Add support for `test.failing.each` ([#13142](https://github.com/facebook/jest/pull/13142)) - `[jest-config]` [**BREAKING**] Make `snapshotFormat` default to `escapeString: false` and `printBasicPrototype: false` ([#13036](https://github.com/facebook/jest/pull/13036)) - `[jest-config]` [**BREAKING**] Remove undocumented `collectCoverageOnlyFrom` option ([#13156](https://github.com/facebook/jest/pull/13156)) -- `[jest-environment-jsdom]` **BREAKING**] Upgrade to `jsdom@20` ([#13037](https://github.com/facebook/jest/pull/13037), [#13058](https://github.com/facebook/jest/pull/13058)) +- `[jest-environment-jsdom]` [**BREAKING**] Upgrade to `jsdom@20` ([#13037](https://github.com/facebook/jest/pull/13037), [#13058](https://github.com/facebook/jest/pull/13058)) - `[@jest/globals]` Add `jest.Mocked`, `jest.MockedClass`, `jest.MockedFunction` and `jest.MockedObject` utility types ([#12727](https://github.com/facebook/jest/pull/12727)) - `[jest-mock]` [**BREAKING**] Refactor `Mocked*` utility types. `MaybeMockedDeep` and `MaybeMocked` became `Mocked` and `MockedShallow` respectively; only deep mocked variants of `MockedClass`, `MockedFunction` and `MockedObject` are exported ([#13123](https://github.com/facebook/jest/pull/13123), [#13124](https://github.com/facebook/jest/pull/13124)) - `[jest-mock]` [**BREAKING**] Change the default `jest.mocked` helper’s behavior to deep mocked ([#13125](https://github.com/facebook/jest/pull/13125)) From 8338c1fddd13f655ea447350a9e1b878579ed0d4 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 30 Aug 2022 11:53:43 +0200 Subject: [PATCH 6/6] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9299c4babc18..80867e863a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Fixes -- `[babel-plugin-jest-hoist]` Support imported `jest` in mock factory ([#13188](https://github.com/facebook/jest/pull/13188 +- `[babel-plugin-jest-hoist]` Support imported `jest` in mock factory ([#13188](https://github.com/facebook/jest/pull/13188)) ### Chore & Maintenance