'Configuring Jest with Rollup and Typescript

I created an app within a monorepo (Lerna) using yarn workspace.

The architecture of the app is as follow:

my-monorepo
├── node_modules
├── packages
│   ├── package1(shared components)
│   ├── package2(other package consuming the shared components)
│   │     └── ./jest.config.js
├── package.json

The problem

the problem is jest is throwing the following error when trying to use package1 in package2 in any test, and I haven't found a way to fix it.

● Test suite failed to run

    Jest encountered an unexpected token

    This usually means that you are trying to import a file which Jest ca
nnot parse, e.g. it's not plain JavaScript.

    By default, if Jest sees a Babel config, it will use that to transfor
m your files, ignoring "node_modules".

    Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io
/docs/en/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can spe
cify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option i
n your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets
) you can stub them out with the "moduleNameMapper" config option.

    You'll find more details and examples of these config options in the
docs:
    https://jestjs.io/docs/en/configuration.html

    Details:

    C:\Users\my-user\Desktop\my-monorepo\node_module
s\antd\es\date-picker\generatePicker\index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__fi
lename,global,jest){import _extends from "@babel/runtime/helpers/esm/exte
nds";

                    ^^^^^^

According the error, I'm trying to import a file which Jest cannot parse, so the problem comes from the package1, so the first thing that comes to my mind is: maybe I'm doing something wrong in rollup in package1 and the final bundle comes in some format that jest doesn't understand...

Jest config

Jest config located in package2, where i want to consume package1:

// jest.config.js in package2

const config = {
  roots: ['src'],
  setupFilesAfterEnv: ['./jest.setup.ts'],
  moduleFileExtensions: ['ts', 'tsx', 'js'],
  testPathIgnorePatterns: ['node_modules/'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
  testMatch: ['**/*.test.(ts|tsx)'],
  moduleNameMapper: {
    // Mocks out all these file formats when tests are run.
    '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      'identity-obj-proxy',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
};

export default config;

Rollup config

This is the rollup configuration in package1:

// rollup.config.js

import peerDepsExternal from 'rollup-plugin-peer-deps-external';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from 'rollup-plugin-typescript2';
import postcss from 'rollup-plugin-postcss';
import copy from 'rollup-plugin-copy';
import json from '@rollup/plugin-json';
import svgr from '@svgr/rollup';
import { babel } from '@rollup/plugin-babel';
import { visualizer } from 'rollup-plugin-visualizer';

import pkg from './package.json';

export default {
  input: 'src/index.tsx',
  output: [
    {
      file: pkg.main,
      format: 'cjs',
      exports: 'named',
      sourcemap: true,
    },
    {
      file: pkg.module,
      format: 'esm',
      exports: 'named',
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal({
      includeDependencies: true,
    }),
    json(),
    svgr(),
    resolve({ extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] }),
    commonjs({
      include: /\**node_modules\**/,
    }),
    // UPDATE 3: Add babel
    babel({
      babelHelpers: 'bundled',
    }),
    typescript({
      useTsconfigDeclarationDir: true,
      exclude: ['*.d.ts', '**/*.d.ts', '**/*.test.tsx'],
      rollupCommonJSResolveHack: true,
      clean: true,
    }),
    postcss({
      extensions: ['.css', '.less'],
      use: {
        less: { javascriptEnabled: true, modifyVars: {} },
      },
    }),
    visualizer({ filename: 'stats-visualizer.html', gzipSize: true }),
    copy({
      targets: [
        {
          src: 'src/styles/themes/dark-variables.less',
          dest: 'dist',
          rename: 'theme/dark.less',
        },
        {
          src: 'src/styles/themes/light-variables.less',
          dest: 'dist',
          rename: 'theme/light.less',
        },
        {
          src: 'src/assets/icons',
          dest: 'dist',
          rename: 'assets/icons',
        },
      ],
    }),
  ],
};

UPDATE 1:

I've tried to use transform rules in jest.config.js as mentioned by Matt Carlota to transpile antdbut this doesn't work:

// jest.config.js
const config = {
  // ... other jest settings
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
    'node_modules/antd/.+\\.(j|t)sx?$': 'ts-jest',
  },
  // I've tried with `antd/es` pattern too and doesn't work
  transformIgnorePatterns: ['node_modules/(?!antd)'],
};

UPDATE 2:

Change manually antd/es by antd/lib in package1 resolve the problem temporarily, but there is one problem and that is that we are a large group of people working and it could be put as a convention to use only antd/lib but I feel it would be error prone.

every time someone forgets to use antd/lib and uses antd/en all tests break with the original error.

UPDATE 3:

Add babel config file and plugin in rollup configuration...

// babel.config.js

module.exports = {
  plugins: [['import', { libraryName: 'antd', libraryDirectory: 'lib' }, 'antd']],
};



Solution 1:[1]

I've been having the same issue for the longest time and finally found a way.

The missing piece in my case was adding moduleNameMapper. A guide mentioned that here.

The solution doesn't require babel-jest.

jest.config.js

const path = require('path');
const { lstatSync, readdirSync } = require('fs');
// get listing of packages in the mono repo
const basePath = path.resolve(__dirname, '..', '..', 'packages');
const packages = readdirSync(basePath).filter((name) => {
  return lstatSync(path.join(basePath, name)).isDirectory();
});

module.exports = {
  preset: 'ts-jest',
  verbose: true,
  moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'ts', 'tsx'],
  moduleDirectories: ['node_modules', 'src'],
  moduleNameMapper: {
    ...packages.reduce(
      (acc, name) => ({
        ...acc,
        [`@xyz/${name}(.*)$`]: `<rootDir>/../../packages/./${name}/src/$1`,
      }),
      {}
    ),
  },
  rootDir: './',
  testRegex: '.spec.ts$',
  transform: {
    '^.+\\.(t)s$': 'ts-jest',
  },
  testEnvironment: 'node',
  setupFilesAfterEnv: ['./jest.setup.ts'],
  testMatch: null,
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.jest.json',
    },
  },
  roots: ['<rootDir>'],
  transformIgnorePatterns: [],
  collectCoverage: false,
  collectCoverageFrom: ['src/**/*.{js{,x},ts{,x}}', '!src/server/index.ts'],
};

tsconfig.json

{
  "compilerOptions": {
    "module": "esnext",
    "target": "ES2019",
    "lib": ["ES2019", "DOM"],
    "noEmit": true,
    "types": ["node", "jest"],
    "rootDir": "./"
    "paths": {
      "@xyz/*": ["packages/*/src"]
    },
    
  },
  "include": ["test/**/*.ts", "src/**/*.ts", "**/*.spec.ts"],
  "references": [
    { "path": "../../packages/package1" },
  ]
}

package.json

...
scripts: {
  "test": "NODE_ENV=production tsc --build ./tsconfig.jest.json && jest --env=node test --watch",
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 bhr