Search code examples
reactjstypescriptjestjsbabeljsenzyme

Jest encountered an unexpected token (React, Typescript, Babel, Jest & Webpack setup)


I've setup a project using React, Typescript, Babel, Webpack, with Prettier & EsLint. I set the project up from scratch and had everything working nicely. I've just started to use Jest however and it all seems to have fallen over. I've seen a number of questions in SO relating to this same error that I'm having, however none of the answers to the same questions have helped me at all. I feel I've wasted a day of my life on this already, and all I'm doing is bloating my config files by adding additional settings that don't seem to help. I have a feeling the issue relates to my particular project configuration, but I'm not sure how to debug or figure out where the issue actually lies.

Inside my package.json file (Jest, Enzyme & Babel related dependencies):

    "@babel/core": "^7.5.5",
    "@babel/plugin-proposal-class-properties": "^7.5.5",
    "@babel/plugin-transform-runtime": "^7.5.5",
    "@babel/polyfill": "^7.4.4",
    "@babel/preset-env": "^7.5.5",
    "@babel/preset-react": "^7.0.0",
    "@babel/preset-typescript": "^7.3.3",
    "@types/enzyme": "^3.10.3",
    "@types/enzyme-adapter-react-16": "^1.0.5",
    "@types/jest": "^24.0.18",
    "@types/shallowequal": "^1.1.1",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^24.9.0",
    "babel-loader": "^8.0.6",
    "babel-plugin-dynamic-import-node": "^2.3.0",
    "babel-plugin-transform-es2015-destructuring": "^6.23.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "enzyme": "^3.10.0",
    "enzyme-adapter-react-16": "^1.14.0",
    "enzyme-to-json": "^3.4.0",
    "jest": "^24.9.0",
    "jest-environment-jsdom": "^24.9.0",
    "jest-environment-jsdom-global": "^1.2.0"
},

jest.config.js:

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { defaults } = require('jest-config');
module.exports = {
  preset: 'ts-jest',
  globals: {
    'ts-jest': {
      diagnostics: false
    }
  },
  verbose: true,
  snapshotSerializers: [
    "<rootDir>/node_modules/enzyme-to-json/serializer"
  ],
  transform: {
    "^.+\\.js?$": "babel-jest",
    "^.+\\.(ts|tsx)?$": "ts-jest"
   },
  testRegex: "(.(test))\\.(ts|tsx)$",
  transformIgnorePatterns: [
    "^.+\\.js$"
  ],
  moduleFileExtensions: [ ...defaults.moduleFileExtensions, "ts", "tsx", "js", "json" ],
  moduleDirectories: [
    "node_modules",
    "src"
  ],
  moduleNameMapper: { 
    "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/assetTransformer.js", 
    "\\.(css|less|scss)$": "<rootDir>/assetTransformer.js" 
  },
  setupFilesAfterEnv: [
    "<rootDir>/jest.setup.js"
  ],
  coveragePathIgnorePatterns: [
    "/node_modules",
    "/src/root/i18n",
    "jest.setup.js"
  ],
  testEnvironment: "jsdom",
  testMatch: ["**/__tests__/**/*.ts?(x)", "**/?(*.)+(test).ts?(x)"],
  testURL: "http://localhost:8080"
}

jest.setup.js:

// Import adapter for enzyme
import Enzyme, { configure, shallow, mount, render } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-localstorage-mock';

configure({ adapter: new Adapter() });
export { shallow, mount, render };
export default Enzyme;

// Log all jsDomErrors when using jsdom testEnvironment
window._virtualConsole &&
  window._virtualConsole.on('jsdomError', function(error) {
    console.error('jsDomError', error.stack, error.detail);
  });

process.env = Object.assign(process.env); 

babel.config.js:

module.exports = {
  "presets": [
    ['@babel/preset-env', {targets: {node: 'current'}}],
    ['@babel/env', {loose:true}],
    '@babel/preset-typescript',
    '@babel/typescript', 
    '@babel/react'
  ],
  "plugins": [
    ['@babel/proposal-class-properties', {loose:true}],
    "dynamic-import-node",
    "proposal-object-rest-spread",
    "transform-class-properties",
    "syntax-class-properties",
    "transform-es2015-modules-commonjs",
    "babel-plugin-transform-es2015-destructuring",
    "babel-plugin-transform-object-rest-spread"
  ],
  "env":{
    "test": {
      "presets": [
        ['@babel/preset-env', {targets: {node: 'current'}}],
        ['@babel/env', {loose:true}],
        '@babel/preset-typescript',
        '@babel/typescript', 
        '@babel/react'
      ],
      "plugins": [
        ['@babel/proposal-class-properties', {loose:true}],
        "dynamic-import-node",
        "proposal-object-rest-spread",
        "transform-class-properties",
        "syntax-class-properties",
        "transform-es2015-modules-commonjs",
        "babel-plugin-transform-es2015-destructuring",
        "babel-plugin-transform-object-rest-spread"
      ],
    }
  }
}

assetTransformer.js:

// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');

module.exports = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  process(src, filename, config, options) {
    return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
  },
};

component.test.tsx file that I am trying to test:

import React from 'react';
import { shallow } from 'enzyme';

import { Component} from './component';

describe ('Component', () => {
  it('should render correctly with default parameters passed', () => {
    const component = shallow(<Component />);
    expect(component).toMatchSnapshot();
  });
});

Error message that I keep seeing no matter what changes I make to any config file:

Test suite failed to run

    Jest encountered an unexpected token

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

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

    Here's what you can do:
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in 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:

    SyntaxError: ...\component.test.tsx: Unexpected token (9:30)

       7 | describe ('Component', () => {
       8 |   it('should render correctly with default parameters passed', () => {
    >  9 |     const component = shallow(<Component />);
         |                               ^
      10 |     expect(component).toMatchSnapshot();
      11 |   });
      12 | });

      at Parser.raise (node_modules/@babel/parser/lib/index.js:6325:17)
      at Parser.unexpected (node_modules/@babel/parser/lib/index.js:7642:16)
      at Parser.parseExprAtom (node_modules/@babel/parser/lib/index.js:8841:20)
      at Parser.parseExprSubscripts (node_modules/@babel/parser/lib/index.js:8412:23)
      at Parser.parseMaybeUnary (node_modules/@babel/parser/lib/index.js:8392:21)
      at Parser.parseExprOps (node_modules/@babel/parser/lib/index.js:8267:23)
      at Parser.parseMaybeConditional (node_modules/@babel/parser/lib/index.js:8240:23)
      at Parser.parseMaybeAssign (node_modules/@babel/parser/lib/index.js:8187:21)
      at Parser.parseExprListItem (node_modules/@babel/parser/lib/index.js:9491:18)
      at Parser.parseCallExpressionArguments (node_modules/@babel/parser/lib/index.js:8621:22)

I'm using VSCode on a Windows 10 machine. The project runs fine, and I can keep developing components and continue with the project. I just can't configure Jest...

Issue is replicated by simply running jest in the terminal in VSCode.

I expect the single test to pass, and create the snapshot of the component.


Solution

  • So after doing some further digging, I managed to find the answer and get this working. I hope this helps someone else who may stumble onto this in the future:

    1. testRegex & testMatch shouldn't be used together. So I removed testRegex from my jest.config.js file.

    2. Update jest.setup.js file. From:

    // Import adapter for enzyme
    import Enzyme, { configure, shallow, mount, render } from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    import 'jest-localstorage-mock';
    
    configure({ adapter: new Adapter() });
    export { shallow, mount, render };
    export default Enzyme;
    

    To:

    const Enzyme = require('enzyme');
    const Adapter = require ('enzyme-adapter-react-16');
    
    Enzyme.configure({ adapter: new Adapter() });
    
    1. In my tsconfig.json file, I had specified paths for shortcut purposes as follows (I missed this in the information above):
    "paths": {              
          "@App/*": ["src/*"],
          "@selectors/*": [ "ts/selectors/*" ],
          "@actions/*": [ "ts/actions/*" ],
          "@components/*": [ "ts/components/*" ],
          "@middleware/*": [ "ts/middleware/*" ],
          "@store/*": [ "ts/store/*" ],
          "@reducers/*": [ "ts/reducers/*" ]
        }
    

    These need to be added to the jest.config.js file, otherwise jest seems to struggle to read the '@store/' declarations.

    Added these under 'moduleNameMapper':

    moduleNameMapper: { 
        "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/assetTransformer.js", 
        "\\.(css|less|scss)$": "<rootDir>/assetTransformer.js",         
        "@App/(.*)": "<rootDir>/src/$1",
        "@selectors/(.*)": "<rootDir>/src/ts/selectors/$1",
        "@actions/(.*)": "<rootDir>/src/ts/actions/$1",
        "@components/(.*)": "<rootDir>/src/ts/components/$1",
        "@middleware/(.*)": "<rootDir>/src/ts/middleware/$1",
        "@store/(.*)": "<rootDir>/src/ts/store/$1",
        "@reducers/(.*)": "<rootDir>/src/ts/reducers/$1"
      }
    
    1. Forgot to add jest.config.js, assetTransformer.js & babel.config.js to eslintignore, so added those in there too (probably unrelated, but needed to be done anyway).

    And yay... test runs.