vercel / next.js

The React Framework
https://nextjs.org
MIT License
125.18k stars 26.74k forks source link

Jest tests do not pass since version v12.1.1 when importing SVG-files and using it like components #37542

Closed Moranilt closed 2 years ago

Moranilt commented 2 years ago

Verify canary release

Provide environment information

Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:37 PDT 2022; root:xnu-8020.121.3~4/RELEASE_ARM64_T6000 Binaries: Node: 16.13.0 npm: 8.10.0 Yarn: N/A pnpm: N/A Relevant packages: next: 12.1.6 react: 17.0.2 react-dom: 17.0.2

What browser are you using? (if relevant)

Chrome

How are you deploying your application? (if relevant)

next start

Describe the Bug

I was updating my packages and I wanted to upgrade NextJS version. But after upgrade I've got test errors, a bit strange errors, like: Check the render method of styled(Icon). I thought that I had an issue with styled-components plugin, but this decision was incorrect.

Later I found out that the reason was SVG-imports that was working with version 12.1.0 but not 12.1.1 and greater only in Jest tests.

Expected Behavior

Expected Jest tests to work with NextJS 12.1.1 or greater and SVG imports with babel-plugin-inline-react-svg. It was working with NextJS v12.1.0 or lower.

To Reproduce

To reproduce it you should create a test and Import any SVG-file using babel-plugin-inline-react-svg and following dependencies:

{
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^12.1.5",
    "@testing-library/user-event": "^13.5.0",
    "@types/react": "^17.0.39",
    "@types/react-dom": "^17.0.11",
    "@types/styled-components": "^5.1.25",
    "babel-jest": "^27.5.1",
    "babel-plugin-inline-react-svg": "^2.0.1",
    "babel-plugin-styled-components": "^2.0.7",
    "jest": "^27.5.1",
    "next": "^12.1.2",
    "react": "^17.0.2",
    "react-dom": "17.0.2",
    "styled-components": "^5.3.3",
    "typescript": "^4.5.5",
}

.babelrc:

{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true }], ["inline-react-svg", {"svgo": false}]]
}

jest.config.json:

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
const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testMatch: ['**/*.test.tsx'],
  moduleNameMapper: {
    // Handle module aliases (this will be automatically configured for you soon)
    '^@components/(.*)$': '<rootDir>/components/$1',
    '^@store/slices/(.*)$': '<rootDir>/components/redux-storage/slices/$1',
    '^@store/hooks': '<rootDir>/components/redux-storage/hooks',
    '^@store/local': '<rootDir>/components/redux-storage/localStorage',
    '^@store': '<rootDir>/components/redux-storage/store',
    '^@layouts/(.*)$': '<rootDir>/components/layouts/$1',
    '^@containers/(.*)$': '<rootDir>/containers/$1',
    '^@utils/(.*)$': '<rootDir>/utils/$1',
  },
  testEnvironment: 'jest-environment-jsdom'
}

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)

jest.setup.js:

// Optional: configure or set up a testing framework before each test.
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`

// Used for __tests__/testing-library.js
// Learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});

Object.defineProperty(window, 'fetch', {
  value: jest.fn().mockImplementation(() => {
    return {
      json: () => Promise.resolve({}),
    };
  }),
});
global.URL.createObjectURL = jest.fn();
global.URL.revokeObjectURL = jest.fn();

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "declaration": true,
    "sourceMap": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "strict": true,
    "noFallthroughCasesInSwitch": true,
    "suppressImplicitAnyIndexErrors": true,
    "noImplicitAny": true,
    "strictFunctionTypes": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": true,
    "jsx": "preserve",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "downlevelIteration": true,
    "allowJs": true,
    "isolatedModules": true,
    "noEmit": true,
    "forceConsistentCasingInFileNames": true,
    "baseUrl": ".",
    "paths": {
      "@store": [
        "components/redux-storage/store"
      ],
      "@store/slices/*": [
        "components/redux-storage/slices/*"
      ],
      "@store/hooks": [
        "components/redux-storage/hooks"
      ],
      "@store/local": [
        "components/redux-storage/localStorage"
      ],
      "@components/*": [
        "components/*"
      ],
      "@layouts/*": [
        "components/layouts/*"
      ],
      "@containers/*": [
        "containers/*"
      ],
      "@utils/*": [
        "utils/*"
      ],
      "next": [
        "node_modules/next"
      ]
    },
    "incremental": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    "pages/_document.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

You should use your SVG-file as Component to reproduce this bug. With this code I'm getting an error:

import IconGooglePlay from '../images/icons/app/IconGooglePlay.svg';

const Footer: React.FC = () => {
    return (
        ...
        <IconGooglePlay />
        ...
    )
};

Write any simple test and run it with jest --watch. You will get an error like this:

Element 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 `Footer`.
balazsorban44 commented 2 years ago

This is because you are probably using a non-default SVG loader.

Please see previous related issues/comments:

https://github.com/vercel/next.js/pull/36907#pullrequestreview-973625856 https://github.com/vercel/next.js/issues/35634#issuecomment-1080942525

liamlows commented 2 years ago

Currently running into the same issue, I'm using SVGR and have it configured in my webpack but thats about it. I import the SVGs as react components. Is SVGR not a default SVG loader? What is the proper way to do this?

balazsorban44 commented 2 years ago

Please see the above comments. There is a workaround suggestion.

Moranilt commented 2 years ago

Currently running into the same issue, I'm using SVGR and have it configured in my webpack but thats about it. I import the SVGs as react components. Is SVGR not a default SVG loader? What is the proper way to do this?

@liamlows The solution that worked for me is to replace default SVG-files with ReactComponents that render the SVG-code. SVG-files have been coverted to ReactComponents.

github-actions[bot] commented 2 years ago

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.