Search code examples
reactjsjestjsjsxvitereact-testing-library

Problem rendering component generated using vite-plugin-svgr in a jest test using testing-library/react render method


I am developing a React APP using the development environment Vite. Since Vite is well supported I included several plugins that work pretty well with it, particularly one that give me the opportunity for creating components directly importing an *.svg in a particular format. It is called vite-plugin-svgr and you can create a component in the following way just including ?react at the end:

import Lock from '../assets/icons/lock.svg?react'

Once I did that I then started to develop some tests with Jest and testing-library/react and it looks like the method "render" from testing-library/react does not like how the components are created using the plugin.

Basically it throws the following error: `console.error Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

    Check the render method of `Widget`.
        at repositoryData (/Users/joscormir/Dev/dashboard_project/src/components/Widget.jsx:13:26)
        at section
        at Dashboard (/Users/joscormir/Dev/dashboard_project/src/components/Dashboard.jsx:9:61)
    
      24 |           {repositoryData.organization.login}/{repositoryData.name}
      25 |         </a>
    > 26 |         {repositoryData.private ? <Lock /> : <UnLock />}
         |                                              ^
      27 |       </header>
      28 |
      29 |       <div className={styles.widget__body}>`

Take into account <UnLock /> is a component created in the way explained before as: import UnLock from '../assets/icons/unlock.svg?react' and Widget.jsx is the component that includes the <UnLock /> component.

It is clearly having troubles rendering that component.

Since this is pretty straight forward I thought about using a Jest plugin called jest-transformer-svg

import React from 'react';
import MySvg from '../images/an-image.svg';

function MyComponent() {
  return (
    <div>
      <MySvg style={{ color: 'blue' }} />
    </div>
  );
}

Then since the extension of my files are a little bit different I tried to use Jest transform the extension\*.svg?reactto meet the requirements of the example above including the following sentence in myjest.config.js\ file.

'^.+\\.(svg\\?react)$': 'jest-transformer-svg'

But it did not work. The whole Test suit fails to work since it says now it cannot find the module declare with the *.svg\?react extensions what means the jest-transformer-svg is not quite working from my understanding


Solution

  • I found a way to make this work. It works like a charm and I found it reading the issues in the vite-plugin-svgr. The key point here is to substitute the .svg?react extension for .svg. This is possible modifying the way we include in vite.config.jsthe call of the plugin.

    We need to modify it in the following way:

    import { defineConfig } from 'vite'
    import react from '@vitejs/plugin-react-swc'
    import svgr from 'vite-plugin-svgr'
    
    export default defineConfig({
      plugins: [react(), svgr({ include: '**/*.svg' })],
    })

    The key is the way we include the svg files, with this modification we can change how we were importing components from using UnLock from '../assets/icons/unlock.svg?react' to use UnLock from '../assets/icons/unlock.svg'

    With this modification we can now properly use jest-transformer-svg since it will interpret correctly the .svg files including in jest.config.js file the transform rule telling the jest transformer to transform all the files with that extensions '^.+\\.svg$': 'jest-transformer-svg':

    module.exports = {
      testEnvironment: 'jsdom',
      setupFilesAfterEnv: ['<rootDir>/tests/setupTests.js'],
      testMatch: ['<rootDir>/tests/**/*.(test).(js|jsx)'],
      testPathIgnorePatterns: ['<rootDir>/tests/e2e/'],
      transform: {
        '^.+\\.(js|jsx|ts|tsx)$': [
          '@swc/jest',
          {
            sourceMaps: true,
            jsc: {
              parser: {
                syntax: 'ecmascript',
                jsx: true,
              },
              transform: {
                react: {
                  runtime: 'automatic',
                },
              },
            },
          },
        ],
        '^.+\\.svg$': 'jest-transformer-svg',
      },
      moduleNameMapper: {
        '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
        '\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
          'jest-transform-stub',
      },
    }

    The above is my configuration files shared just in order to clarify where the transform rule must be placed, everything else could change depending on your own jest configuration.