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

Add hoisting of the "@jest/globals" import together with jest.mock #120

Closed
webschik opened this issue Sep 1, 2022 · 13 comments
Closed

Add hoisting of the "@jest/globals" import together with jest.mock #120

webschik opened this issue Sep 1, 2022 · 13 comments

Comments

@webschik
Copy link

webschik commented Sep 1, 2022

In the next example the hoisting of the jest.mock doesn't work:

// input
import {jest} from "@jest/globals";
import {foo} from './module1.js';
import {bar} from './module2.js';

jest.mock('./module1.js');
// result code
"use strict";
var _globals = require("@jest/globals");
var _module1Js = require("./module1.js");
var _module2Js = require("./module2.js");
_globals.jest.mock('./module1.js');

Is it possible to update swc AST to hoist jest.mock before the module1.js and module2.js imports?

@webschik webschik changed the title Add hoisting of the "@jest/globals" import before jest.mock Add hoisting of the "@jest/globals" import together with jest.mock Sep 1, 2022
@koshic

This comment was marked as spam.

@Smrtnyk
Copy link
Contributor

Smrtnyk commented Oct 19, 2023

@kdy1 any plans on this?
I have a repo with repro you can just run in here
https://github.com/Smrtnyk/swc-jest-globals-repro

@kdy1 kdy1 closed this as not planned Won't fix, can't repro, duplicate, stale Oct 19, 2023
@kdy1
Copy link
Member

kdy1 commented Oct 19, 2023

See swc-project/swc#7435

@Smrtnyk
Copy link
Contributor

Smrtnyk commented Oct 19, 2023

I don't see what that document is supposed to reveal here?
This is about the jest.mock hositing which is already built in in swc, but doesn't cover this case
this basically breaks the functionality of jest

@kdy1
Copy link
Member

kdy1 commented Oct 19, 2023

Is it possible to update swc AST to hoist jest.mock before the module1.js and module2.js imports?

I think you are talking about something different

@Smrtnyk
Copy link
Contributor

Smrtnyk commented Oct 19, 2023

I think the easiest is to remove import {jest} from "@jest/globals"; completely since it is not needed for jest.
Jest injects globals anyway.
It is only for typecript to have proper strict types in tests.
And hoist jest.mock as it is already hoisted without @jest/globals imports

If you check the repo I linked, tests work if you remove that import (breaks typescript types) but tests break if import is there

@kdy1
Copy link
Member

kdy1 commented Oct 19, 2023

It's a different issue from this. Please file a new one instead.

@Smrtnyk
Copy link
Contributor

Smrtnyk commented Oct 19, 2023

I don't see how it is different
I see it as exactly the same issue, check my repository I linked
@webschik is the issue I am talking about the same as you are referring to?

@kdy1
Copy link
Member

kdy1 commented Oct 19, 2023

He wants to mock ESMs, and it will never be supported. If your requirement is the same, it's also not planned.

@kdy1
Copy link
Member

kdy1 commented Oct 19, 2023

Ah it was the same issue.
It'll never be supported, and I'll fix @swc/core to never allow mocking ESMs, even if the global import does not exist

@Smrtnyk
Copy link
Contributor

Smrtnyk commented Oct 19, 2023

ok let me explain myself
the issue is with jest.mock hoisting

swc already hoists jest.mock by default
but if you import jest from @jest/globals to get good types for typescript
swc stops hoisting jest.mock

that import is there purely for typescript, not for jest itself
tests work if you remove that import but types are not good

so what that import does is, it prevents swc from hoisting jest.mock properly as it was before
it is not about esm stuff, it is purely and only about jest.mock hoisting.

If swc would move jest.mock under that import, or remove that import completely and hoist jest.mock as before, then the behavior would be preserved.

@SimenB
Copy link
Contributor

SimenB commented Oct 19, 2023

Jest injects globals anyway.

Can be turned off via config. https://jestjs.io/docs/cli#--injectglobals

A quick hack might be to just remove the import, run the transform/hoisting, then reinject the import afterwards. Would be wrong if people alias the imports, but still

@Smrtnyk
Copy link
Contributor

Smrtnyk commented Oct 19, 2023

what I ended up doing is to write this kind of transfomer
seems like it works

const { createTransformer } = require("@swc/jest");

/**
 * Transformer for adapting imports from "@jest/globals" which only exist to provide strict types for jest api
 * Before running swc we need to adapt them, because swc does not properly hoist jest.mock in case imports from
 * `@jest/globals` are present
 * We don't strip it, because we need to preserve original code for source maps to continue working, so we just prefix
 * imports with `_`
 */
const removeJestGlobalsTransformer = {
    process(src, filename) {
        if (!src.includes("@jest/globals")) {
            return src;
        }
        return src.replace(/import\s+{([\s\S]*?)}\s+from\s+['"]@jest\/globals['"];/gs, (match, p1) => {
            // Prefix each imported name with an underscore while maintaining their original positions
            const names = p1.split(",").map(name => "_" + name.trim());
            const renamedImports = names.join(",\n ");
            return `import {\n ${renamedImports}\n} from "@jest/globals";`;
        });
    }
};

const swcOptions = {
    sourceMaps: "inline",
    module: {
        strict: false,
        strictMode: false
    },
    jsc: {
        target: "es5",
        parser: {
            syntax: "typescript",
            dynamicImport: true
        },
        experimental: {
            plugins: [["jest_workaround", {}]]
        }
    }
};

const swcTransformer = createTransformer(swcOptions);

const chainedTransformers = {
    process(src, filename, config, transformOptions) {
        const afterJestGlobalsRemoval = removeJestGlobalsTransformer.process(src, filename);

        return swcTransformer.process(afterJestGlobalsRemoval, filename, config, transformOptions);
    }
};

module.exports = chainedTransformers;

I kept the injection of globals, but just slightly modified the imports so swc hoisting of jest.mock keeps working
I guess there is a better way, but seems like this does the trick, and stopping on break points in intellij in test cases still works when working with typescript

Regex might be needed to be adapted, by some better regex magic skills

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

No branches or pull requests

5 participants