15

I am working on a Next.js project using TypeScript and for testing I use Jest and React Testing Lib. However, I encounter a SyntaxError: Cannot use import statement outside a module for components where I import rehype-raw.

As far as I understand this, Jest does not support ES6 so node_modules may need to be transformed. This can be configured using transformIgnorePatterns. For example if rehype-raw is causing this error using "transformIgnorePatterns": ["node_modules/(?!rehype-raw)/"] should allow transformation of the rehype-raw but no other module. And thus solve this error.

However, this does not work for me. But idk why and how I can solve this. No suggested solution I have found could solve this problem. I have attached my error output, jest.config.js and babel.rc file below.

Error output

 FAIL  test/pages/companies/[id].test.tsx                  
  ● Test suite failed to run

    Jest encountered an unexpected token

    [...]

    Details:

    /path/frontend-job/node_modules/rehype-raw/index.js:7
    import {raw} from 'hast-util-raw'
    ^^^^^^

    SyntaxError: Cannot use import statement outside a module

      3 | import Image from 'next/image';
      4 | import { Remark } from 'react-remark';
    > 5 | import rehypeRaw from 'rehype-raw';
        | ^
      6 | import rehypeSanitize from 'rehype-sanitize';
      7 | import { logError } from '@utils/logger';
      8 |

      at Runtime.createScriptFromCode (node_modules/jest-runtime/build/index.js:1728:14)
      at Object.<anonymous> (components/markdown/JobMarkdown.tsx:5:1)

jest.config.js

const { resolve } = require('path');

module.exports = {
  roots: ['<rootDir>'],
  moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'],
  setupFiles: ['<rootDir>/test/setup.js'],
  testPathIgnorePatterns: ['<rootDir>[/\\\\](node_modules|.next)[/\\\\]'],
  transform: {
    '^.+\\.(ts|tsx)$': 'babel-jest',
  },
  transformIgnorePatterns: [
    'node_modules/(?!rehype-raw)/',
  ],
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname',
  ],
  moduleNameMapper: {
    // Force mocks: https://github.com/facebook/jest/issues/4262
    '@api/axios': '<rootDir>/test/__mocks__/axios.js',
    // Normal module aliases
    '\\.(css|less|sass|scss)$': 'identity-obj-proxy',
    '\\.(gif|ttf|eot|svg|png)$': '<rootDir>/test/__mocks__/fileMock.js',
    '^@test/(.*)$': resolve(__dirname, './test/$1'),
    '^@test/faker/(.*)$': resolve(__dirname, './test/faker/$1'),
    '^@components/(.*)$': resolve(__dirname, './components/$1'),
    '^@pages/(.*)$': resolve(__dirname, './pages/$1'),
    '^@utils/(.*)$': resolve(__dirname, './utils/$1'),
    '^@api/(.*)$': resolve(__dirname, './api/$1'),
    '^@store/(.*)$': resolve(__dirname, './store/$1'),
  },
  testEnvironment: 'jsdom',
};

babel.rc

{
  "presets": ["next/babel"]
}
Greeneco
  • 691
  • 2
  • 8
  • 23

4 Answers4

6

Jest does not support ECMAScript Modules which is what hast-util-raw uses. Second problem; transformIgnorePatterns did not work too so below is the fix for myself with a Next.JS setup.

1. Delete babel.rc

Delete your babel.rc. You can just use the below changes.

2. Add moduleNameMapper

This fixes a different error where parse5 can not be found. Required by hast-util-raw

/** @type {import('jest').Config} */
const customJestConfig = {
  //...
  moduleNameMapper: {
    'parse5/lib/parser/index.js':
      '<rootDir>/node_modules/hast-util-raw/node_modules/parse5/lib/parser/index.js',
  }
}

3. Add transformIgnorePatterns

Add transformIgnorePatterns however right at the end. As I said, this did not work adding directly into the config for som reason. I also had to add every package used by hast-util-raw. I am sure there is a better way to do this though :)

module.exports = async () => ({
  ...(await createJestConfig(customJestConfig)()),
  transformIgnorePatterns: [
    'node_modules/(?!(rehype-raw|hast-util-raw|unist-util-position|unist-util-visit|unist-util-visit-parents|unist-util-is|hast-util-from-parse5|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)/)'
  ]
})


My full config if anyone is interested...

/* eslint-disable @typescript-eslint/no-var-requires */
// jest.config.js
const nextJest = require('next/jest')

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './'
})

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const customJestConfig = {
  // Add more setup options before each test is run
  setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/src', '<rootDir>/src/pages'],
  testEnvironment: 'jest-environment-jsdom',
  testPathIgnorePatterns: [
    '<rootDir>/.next/',
    '<rootDir>/node_modules/',
    '<rootDir>/coverage',
    '<rootDir>/dist'
  ],
  moduleNameMapper: {
    'react-markdown': '<rootDir>/node_modules/react-markdown/react-markdown.min.js',
    'parse5/lib/parser/index.js':
      '<rootDir>/node_modules/hast-util-raw/node_modules/parse5/lib/parser/index.js'
  },
  resolver: '<rootDir>/.jest/resolver.js',
  clearMocks: true,
  testTimeout: 20000
}

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
// module.exports = createJestConfig(customJestConfig)
module.exports = async () => ({
  ...(await createJestConfig(customJestConfig)()),
  transformIgnorePatterns: [
    'node_modules/(?!(rehype-raw|hast-util-raw|unist-util-position|unist-util-visit|unist-util-visit-parents|unist-util-is|hast-util-from-parse5|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|vfile-location|web-namespaces|hast-util-to-parse5|zwitch|html-void-elements)/)'
  ]
})

rottitime
  • 1,653
  • 17
  • 29
0

Next has already out-of-the-box support for Jest, I'd suggest you follow the steps provided in the docs.

teo-garcia
  • 111
  • 3
  • 7
    I tried next/js as described in the docs and also extended the custom jest config, but I still get `SyntaxError: Cannot use import statement outside a module` – Greeneco Jan 30 '22 at 17:58
  • 3
    I think using https://github.com/vercel/next.js/discussions/31152#discussioncomment-1697047 I could solve this syntax error but some mocks are not working anymore – Greeneco Jan 30 '22 at 18:20
  • 1
    This thread has some discussion + code I've found valuable when the OP's issue happens for me: https://github.com/remarkjs/react-markdown/issues/635#issuecomment-956158474. Long story short, I just mock the ES6 modules that don't play nicely, where that's appropriate. – Jason R Stevens CFA Feb 24 '22 at 20:34
  • There was a bug in next/jest but it was fixed and everything works for me right now. – Greeneco Mar 20 '22 at 20:01
  • 1
    Can you post the solution if this works please? The docs do not give the fix – rottitime Feb 28 '23 at 16:31
0

What you need to understand is Next.js will tell Jest to not transform packages under /node_modules except those defined in their config file (the transpilePackages property).

It's a bit of a mess to add properly transform exclusions over this, but https://github.com/vercel/next.js/discussions/31152#discussioncomment-1697047 put me on the right track.

If you don't want to manually manage the array since it depends on transpilePackages on the Next.js side, I reused their logic at https://github.com/vercel/next.js/blob/435eca3fc5b5dd23cad6cff43632bdcc777727d9/packages/next/src/build/jest/jest.ts#L156-L173 to transform both those from transpilePackages but also the ones needed for Jest to run properly.

Here the diff of my workaround: https://github.com/inclusion-numerique/mediature/commit/cfe3ad85ab13d3f38d863afb8ee24b6995ae4e00

I'm sorry to not put direct code on this post, it would make things unclear.

Thomas Ramé
  • 438
  • 4
  • 10
-2

Did you already use type:"module" in package.json?

Ronnel
  • 49
  • 1
  • 6