skip to Main Content

I am working in a Vite project, my goal is to be able to write tests. So I setup Jest and Babel and write a test, but I’m getting an error like this:

C:UsersjoaovOneDriveDev ProjetosProjetos pessoaiscsgo-e-commercenode_modulesfirebaseappdistindex.esm.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import { registerVersion } from '@firebase/app';
                                                                                      ^^^^^^ 

    SyntaxError: Cannot use import statement outside a module

    > 1 | import { initializeApp } from 'firebase/app';
        | ^
      2 | import {
      3 |   getAuth,
      4 |   signInWithRedirect,

This is the test I am trying to write:

// ProductCard.test.tsx

import { render, screen } from '@testing-library/react';
import { Theme } from '../../Theme';
import ProductCard from './ProductCard';
import { Provider } from 'react-redux';
import { store } from '../../store/store';
import '@testing-library/jest-dom';

const product = {
  id: 1,
  imageUrl: 'www.google.com',
  name: 'Dragon Lore',
  price: 90,
};

describe('ProductCard', () => {
  it('should render the Product Card', () => {
    render(
      <Provider store={store}>
        <Theme>
          <ProductCard product={product} />
        </Theme>
      </Provider>
    );

    const nameElement = screen.getByText(/dragon lore/i);
    const imageElement = screen.getByAltText(/dragon lore/i);
    const button = screen.getByRole('button', { name: /add to cart/i });

    expect(nameElement).toBeInTheDocument();
    expect(imageElement).toBeInTheDocument();
    expect(button).toBeInTheDocument();
  });
});

Following is my Jest configuration:

// jest.config.js

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jest-environment-jsdom',
  setupFilesAfterEnv: ['<rootDir>/.jest/setup-tests.js'],
  moduleNameMapper: {
    '\.(gif|ttf|eot|svg|png)$': '<rootDir>/.jest/__mocks__/fileMock.js',
    '\.(css|less|sass|scss)$': 'identity-obj-proxy',
  },
  transform: {
    '^.+\.jsx?$': 'babel-jest',
    '^.+\.tsx?$': 'ts-jest',
  },
};

The Babel configuration is:

// babel.config.js

module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { esmodules: true, node: 'current' } }],
  '@babel/preset-typescript',
    ['@babel/preset-react', { runtime: 'automatic' }],
  ],
};

And, the TypeScript configuration is:

{
  "compilerOptions": {
    "target": "es5",
    "types": ["vite/client", "node"],
    "lib": ["dom", "dom.iterable", "esnext"],
    "downlevelIteration": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "CommonJS",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

Finally, this is my package.json file:

{
  "name": "csgo-e-commerce",
  "private": true,
  "version": "0.0.0",
  "type": "commonjs",
  "scripts": {
    "dev": "vite",
    "test": "jest",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@stripe/react-stripe-js": "^1.16.4",
    "@stripe/stripe-js": "^1.46.0",
    "@types/react-router-dom": "^5.3.3",
    "dotenv": "^16.0.3",
    "firebase": "^9.14.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-hook-form": "^7.40.0",
    "react-redux": "^8.0.5",
    "react-router-dom": "^6.4.3",
    "redux": "^4.2.1",
    "redux-persist": "^6.0.0",
    "redux-saga": "^1.2.2",
    "redux-thunk": "^2.4.2",
    "reselect": "^4.1.7",
    "stripe": "^11.10.0",
    "styled-components": "^5.3.6",
    "typed-redux-saga": "^1.5.0",
    "validator": "^13.7.0",
    "vite-plugin-svgr": "^2.4.0"
  },
  "devDependencies": {
    "@babel/core": "^7.21.0",
    "@babel/plugin-transform-modules-commonjs": "^7.21.2",
    "@babel/preset-env": "^7.20.2",
    "@babel/preset-react": "^7.18.6",
    "@babel/preset-typescript": "^7.21.0",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^14.0.0",
    "@testing-library/user-event": "^14.4.3",
    "@types/jest": "^29.5.0",
    "@types/node": "^18.14.2",
    "@types/react": "^18.0.28",
    "@types/react-dom": "^18.0.11",
    "@types/redux-logger": "^3.0.9",
    "@types/styled-components": "^5.1.26",
    "@types/validator": "^13.7.13",
    "@vitejs/plugin-react": "^2.2.0",
    "babel-jest": "^29.4.3",
    "babel-loader": "^8.3.0",
    "babel-plugin-macros": "^3.1.0",
    "eslint": "^8.28.0",
    "eslint-config-airbnb": "^19.0.4",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-import": "^2.26.0",
    "eslint-plugin-jsx-a11y": "^6.6.1",
    "eslint-plugin-react": "^7.31.11",
    "eslint-plugin-react-hooks": "^4.6.0",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^29.5.0",
    "jest-environment-jsdom": "^29.4.3",
    "prettier": "^2.7.1",
    "redux-logger": "^3.0.6",
    "ts-jest": "^29.0.5",
    "typescript": "^4.9.5",
    "vite": "^3.2.3"
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    So the problem was Firebase, I solve the problem with this final config:

    Jest config:

    //jest.config.json
    {
      "preset": "ts-jest",
      "testEnvironment": "jest-environment-jsdom",
      "setupFilesAfterEnv": ["<rootDir>/.jest/setup-tests.js"],
      "moduleNameMapper": {
        "\.(gif|ttf|eot|svg|png)$": "<rootDir>/.jest/__mocks__/fileMock.js",
        "\.(css|less|sass|scss)$": "identity-obj-proxy"
      },
      "transform": {
        "^.+\.jsx?$": "babel-jest",
        "^.+\.tsx?$": "ts-jest"
      },
      "transformIgnorePatterns": ["/node_modules/(?!@firebase)/"]
    }
    

    Babel Config:

    //babel.config.json
    {
      "presets": [
        [
          "@babel/preset-env",
          { "targets": { "esmodules": true, "node": "current" } }
        ],
        "@babel/preset-typescript",
        ["@babel/preset-react", { "runtime": "automatic" }]
      ],
      "plugins": ["@babel/plugin-transform-runtime"]
    }
    

    Package:

    //package.json  
                {
      "name": "csgo-e-commerce",
      "private": true,
      "version": "0.0.0",
      "type": "module",
      "scripts": {
        "dev": "vite",
        "test": "jest",
        "build": "vite build",
        "preview": "vite preview"
      },
      "dependencies": {
        "@stripe/react-stripe-js": "^1.16.4",
        "@stripe/stripe-js": "^1.46.0",
        "dotenv": "^16.0.3",
        "firebase": "^9.14.0",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-hook-form": "^7.40.0",
        "react-redux": "^8.0.5",
        "react-router-dom": "^6.4.3",
        "redux": "^4.2.1",
        "redux-persist": "^6.0.0",
        "redux-saga": "^1.2.2",
        "redux-thunk": "^2.4.2",
        "reselect": "^4.1.7",
        "stripe": "^11.10.0",
        "styled-components": "^5.3.6",
        "typed-redux-saga": "^1.5.0",
        "validator": "^13.7.0",
        "vite-plugin-svgr": "^2.4.0"
      },
      "devDependencies": {
        "@babel/core": "^7.21.0",
        "@babel/plugin-transform-modules-commonjs": "^7.21.2",
        "@babel/plugin-transform-runtime": "^7.21.0",
        "@babel/preset-env": "^7.20.2",
        "@babel/preset-react": "^7.18.6",
        "@babel/preset-typescript": "^7.21.0",
        "@testing-library/jest-dom": "^5.16.5",
        "@testing-library/react": "^14.0.0",
        "@testing-library/user-event": "^14.4.3",
        "@types/jest": "^29.5.0",
        "@types/node": "^18.14.2",
        "@types/react": "^18.0.28",
        "@types/react-dom": "^18.0.11",
        "@types/react-router-dom": "^5.3.3",
        "@types/redux-logger": "^3.0.9",
        "@types/styled-components": "^5.1.26",
        "@types/validator": "^13.7.13",
        "@vitejs/plugin-react": "^2.2.0",
        "babel-jest": "^29.4.3",
        "babel-loader": "^8.3.0",
        "babel-plugin-macros": "^3.1.0",
        "eslint": "^8.28.0",
        "eslint-config-airbnb": "^19.0.4",
        "eslint-config-prettier": "^8.5.0",
        "eslint-plugin-import": "^2.26.0",
        "eslint-plugin-jsx-a11y": "^6.6.1",
        "eslint-plugin-react": "^7.31.11",
        "eslint-plugin-react-hooks": "^4.6.0",
        "identity-obj-proxy": "^3.0.0",
        "jest": "^29.5.0",
        "jest-environment-jsdom": "^29.4.3",
        "prettier": "^2.7.1",
        "redux-logger": "^3.0.6",
        "ts-jest": "^29.0.5",
        "typescript": "^4.9.5",
        "vite": "^3.2.3"
      }
    }
    

    I follow the 'transformIgnorePatterns' suggested, installed 'babel/plugin-transform-runtime' add this line in babel config:

    "plugins": ["@babel/plugin-transform-runtime"]
    

    I ran my tests again and it worked! Thank you!!


  2. There are multiple reasons why this error can happen.

    The most common reason is that the package that you are using doesn’t have "type": "module" property declared in its package.json file. So, in that case, any file with .js extension is treated as CommonJS file which doesn’t allow ESM Syntax – import and export statements. And, the file with .js extension is using ESM syntax.

    The second reason is that the third party package is ESM-only package and the compiler/test-runner/bundler is not compiling the file as it is considered third-party packages. Generally, bundler or compiler would not attempt to compile any file inside node_modules folder.

    The third possible reason is that your package is dual-published supporting both ESM and CommonJS using conditional exports field of package.json. But, your bundler or test runner is probably not picking up the right file in question.

    Now let’s return to your problem. You are using latest version (v29) of Jest meaning exports field is supported and it can also handle ESM modules well.

    That leaves us with first problem where package.json file is not probably declaring if this is a ESM module or not. The problematic package in question is firebase package. If we look inside the package:

    The <ROOT>/node_modules/firebase/app/package.json file, it properly declares THE exports field with conditional exports.

    In your code, somewhere you are importing firebase/app and in your Jest configuration, then environment you are using is jest-environment-jsdom which means it matches the browser condition and it picks following:

    "browser": {
      "require": "./app/dist/index.cjs.js",
      "import": "./app/dist/esm/index.esm.js"
    },
    

    From this it is picking up ./app/dist/esm/index.esm.js as evident from the error message. And, why Jest uses import instead of require is due to the heuristics it is applying (your own code is in ESM and Babel is running in ESM target). This is good so far.

    But right when it picks up ./app/dist/esm/index.esm.js file, the problem begins. Since, this is .js file, Jest doesn’t really know if this is ESM or CommonJS file. The obvious thing that jest will do is to look for nearest package.json file and there is one present here <ROOT>/node_modules/firebase/app/package.json. This is not the same as <ROOT>node_modules/firebase/package.json. This nearest package.json file has following content:

    {
      "name": "firebase/app",
      "main": "dist/index.cjs.js",
      "browser": "dist/esm/index.esm.js",
      "module": "dist/esm/index.esm.js",
      "typings": "dist/app/index.d.ts"
    }
    

    As, you can see this file doesn’t specify "type": "module" field and thus Jest assumes that <ROOT>/firebase/app/dist/esm/index.esm.js is a legacy CommonJS file instead of a new ESM file.

    It is not your mistake per say. But it is rather package author’s mistake that they shipped this additional package.json file. Ideally, one package should have exactly one package.json file. But in earlier days, when things were still infancy, this was a common practice to nest submodules and exploit node resolution algorithm. But with arrival of exports field, this is no longer necessary.

    That is why you get this error. Now to fix this error, you have to tell Jest to transform this file if necessary using the transformIgnorePatterns configuration. I am bit hazy on regular expression but it would be something like this:

    {
      "transformIgnorePatterns": [
        "node_modules/(?!firebase)"
      ],
    }
    

    This is a long answer but I hope it helps you understand the exact cause of the issue.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search