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:\Users\joaov\OneDrive\Dev Projetos\Projetos pessoais\csgo-e-commerce\node_modules\firebase\app\dist\index.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"
}
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With