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

jest.mock is not hoisted if jest is imported from @jest/globals #310

Closed
valeneiko opened this issue Oct 24, 2022 · 6 comments · Fixed by swc-project/swc#8994
Closed

jest.mock is not hoisted if jest is imported from @jest/globals #310

valeneiko opened this issue Oct 24, 2022 · 6 comments · Fixed by swc-project/swc#8994
Assignees

Comments

@valeneiko
Copy link

Describe the bug

If I configure jest to not inject globals (injectGlobals: false) and instead import it explicitly: import { describe, expect, it, jest } from '@jest/globals';

Calls to jest.mock(...) are no longer hoisted to the top of the file.

Input code

import { setTimeout } from 'timers/promises';

import { describe, expect, it, jest } from '@jest/globals';

jest.mock('timers/promises', () => ({
  setTimeout: jest.fn(() => Promise.resolve())
}));

describe('suite', () => {
  it('my-test', () => {
    const totalDelay = jest
          .mocked(setTimeout)
          .mock.calls.reduce((agg, call) => agg + (call[0] as number), 0);
    expect(totalDelay).toStrictEqual(0);
  })
})

Config

{
  "$schema": "https://json.schemastore.org/swcrc",
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": true,
      "dynamicImport": true
    },
    "transform": {
      "react": {
        "runtime": "automatic"
      }
    },
    "baseUrl": "./",
    "target": "es2021",
    "keepClassNames": true
  },
  "sourceMaps": "inline",
  "inlineSourcesContent": true,
  "isModule": true,
  "module": {
    "type": "commonjs",
    "ignoreDynamic": false
  }
}

Playground link

https://play.swc.rs/?version=1.3.10&code=H4sIAAAAAAAAA2WQT0%2FDMAzF7%2FkUvtURpdsZNMQB7pPghjikqVcFkqZLXMQ09buTpBrlT6Qcnp%2F9%2FEuMG31gOEMkfjaO%2FMQwwyF4BxUnHeJmTMJEitWtEObS3lHUwbRUA32OpLkGk%2B4bxXX8PqtNb32rbBnOunFev%2BO%2F6BpQwu4O8CzgB8pNSWwOAy72fulvAkVvPwilFLOUKfqCg1WcDNN3Xo4zjJU7XXNK%2BlUH0H5IvOxZ2Qey6gS7sq94yym41OGKJP%2B6jVbWxoTUTZoQVd%2FXkEtlT1JwBZj1y%2FYVVIRhci0FWcM2ceeQ5f9wpZAN%2BycORvPjcVIWl8Y5P1V8AS8JMp6vAQAA&config=H4sIAAAAAAAAAz2QMW7DMAxF70J0DOw2o9d06ZAuRQ7AKowj16IEkkJrGL57abvp9vn4%2BSVyhicNd0oIHdzNinZtO2jmZqdqWajJ0rf6HSTAAQYN0M1QUJRkVTqx4Y%2BP21RIg8RibjN1ZFLpANeJMcXwlkoW2%2BHiBkHWW5a0ZghhsE1UtpjI07BaTmgxwOLuT1S6yOi8add0lJ58AEiPz8cXJ19E5TSi6jsm0v9XNFcJdMbiCCKPkcnNu%2FjYenrKbMT2%2BG3Uc77WkR51%2BqvmbT9PCTmlzIOuOT37eV73%2FaC74ai0LL%2BwXm0pUwEAAA%3D%3D

Expected behavior

Compiled output to match ts-jest:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const globals_1 = require("@jest/globals");
jest.mock('timers/promises', () => ({
    setTimeout: jest.fn(() => Promise.resolve())
}));
const promises_1 = require("timers/promises");
...

jest.mock(...) call should be hoisted to the top of the file together with its import statement.

Actual behavior

The code is compiled as is, and jest.mock(...) calls are not hoisted. This prevents mocks from working, since the non-mocked module is imported before jest had a chance to mock it.

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
const _promises = require("timers/promises");
const _globals = require("@jest/globals");
_globals.jest.mock('timers/promises', ()=>({
        setTimeout: _globals.jest.fn(()=>Promise.resolve())
    }));

Version

1.3.10

Additional context

When there are no imports from @jest/globals, compiled output from swc is correct -jest.mock(...) calls are hoisted to the top of the file.

@kdy1
Copy link
Member

kdy1 commented Oct 24, 2022

Imports are hoisted because you used ESM syntax

@kdy1 kdy1 closed this as not planned Won't fix, can't repro, duplicate, stale Oct 24, 2022
@valeneiko
Copy link
Author

@kdy1 Can you explain in a bit more detail? I am not sure I understand what you meant.

The input file is a typescript file. I am expecting it to be compiled to CJS.

I was expecting jest.mock(...) to be compiled the same in both of these cases:

  • import {jest} from "@jest/globals"
  • using global jest object

@kdy1
Copy link
Member

kdy1 commented Oct 24, 2022

swc-project/swc#5205

@swc-project swc-project locked as resolved and limited conversation to collaborators Oct 24, 2022
@swc-project swc-project unlocked this conversation Oct 24, 2022
@swc-bot
Copy link
Contributor

swc-bot commented Nov 24, 2022

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@swc-project swc-project locked as resolved and limited conversation to collaborators Nov 24, 2022
@magic-akari magic-akari reopened this May 28, 2024
@swc-project swc-project unlocked this conversation May 28, 2024
@magic-akari magic-akari transferred this issue from swc-project/swc May 28, 2024
@magic-akari
Copy link
Member

This matter may be beyond the scope of the ESM specification. ESM is the standard we adhere to.
However, it should be noted that the Babel plugin will hoist Jest-related methods.
https://github.com/jestjs/jest/blob/b7ae0b85e981eae0e373d98ab2ae8fe497d90027/packages/babel-plugin-jest-hoist/src/index.ts#L31-L37

Additionally, our plugin has also done similar things, but it does not cover everything.

Expr::Ident(i) if i.sym == *"jest" => match prop {

So this should be a technically feasible solution. We can fix this issue in the swc jest plugin.

@kdy1 kdy1 self-assigned this May 28, 2024
@magic-akari
Copy link
Member

The core issue lies here: In Babel, the passes are structured like an onion model, allowing plugins to define both pre and post phases.
https://github.com/jestjs/jest/blob/b7ae0b85e981eae0e373d98ab2ae8fe497d90027/packages/babel-plugin-jest-hoist/src/index.ts#L405-L409

In SWC, passes follows the pipeline model, and re-entering a plugin after the import phase (CommonJS) is a challenge.

kdy1 added a commit to swc-project/swc that referenced this issue May 30, 2024
**Description:**

- We have two `jest` pass. One in `@swc/core` (via _hidden.jest flag), and one in the plugin. This PR fixes only the core one.

However, the linked issue is wrong because the code tries to break the rules of ESM specification. So, although this PR closes the issue, the final form is not the issue the author wanted.

**Related issue:**

 - Closes swc-project/plugins#310
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

4 participants