Search code examples
reactjsnext.jsjestjsreact-testing-libraryswiper.js

Swiper SyntaxError: Cannot use import statement outside a module in React Next App that uses Webpack while testing using RTL


My Entire code:

Component type: (ProductGallery.types.ts)

type productGalleryType = {
  productImages: string[];
};
export default productGalleryType;

Component: (ProductGallery.tsx)

import React, { useState } from 'react';
import { Swiper, SwiperSlide } from 'swiper/react';
import SwiperCore, { Thumbs } from 'swiper';
import productGalleryType from '../../@types/ProductGallery.types';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/thumbs';

const ProductGallery: React.FC<productGalleryType> = ({
  productImages,
}: productGalleryType) => {
  const [activeThumb, setActiveThumb] = useState<SwiperCore>();
  return (
    <>
      <Swiper
        loop={true}
        spaceBetween={10}
        modules={[Thumbs]}
        grabCursor={true}
        thumbs={{
          swiper: activeThumb && !activeThumb.destroyed ? activeThumb : null,
        }}
        className="h-308 w-294 rounded-sm"
      >
        {productImages &&
          productImages.map((item, index) => (
            <SwiperSlide key={index} className="relative overflow-hidden">
              <img
                src={item}
                alt="product gallery image"
                className="m-0 h-full w-full object-cover object-center p-0"
              />
            </SwiperSlide>
          ))}
      </Swiper>
      <Swiper
        onSwiper={setActiveThumb}
        loop={true}
        spaceBetween={10}
        slidesPerView={4}
        modules={[Thumbs]}
        className="mt-52 w-[338px]"
      >
        {productImages &&
          productImages.map((item, index) => (
            <SwiperSlide key={index} className="!mx-10 !h-66 !w-66 rounded-xxs">
              <img
                src={item}
                alt="product gallery thumbnail"
                className="m-0 h-full w-full rounded-xxs object-cover object-center p-0"
              />
            </SwiperSlide>
          ))}
      </Swiper>
    </>
  );
};

export default ProductGallery;

Story: (ProductGallery.stories.tsx)

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import ProductGallery from './ProductGallery';

const productImages = [
  'https://dummyimage.com/1000x400/070be6/fff.png',
  'https://dummyimage.com/900x900/bd09e6/fff.png',
  'https://dummyimage.com/600x600/070be6/fff.png',
  'https://dummyimage.com/1600x1400/ffaadd/fff.png',
  'https://dummyimage.com/500x1000/ff0000/fff.png',
];

export default {
  title: 'ProductGallery',
  Component: ProductGallery,
  argTypes: {},
} as ComponentMeta<typeof ProductGallery>;

const Template: ComponentStory<typeof ProductGallery> = (args) => (
  <ProductGallery {...args} />
);

export const GalleryWithThumbnails = Template.bind({});
GalleryWithThumbnails.args = {
  productImages: productImages,
};

Test: (ProductGallery.test.tsx)

(Causing Problem here)

import { render, screen } from '@testing-library/react';
import ProductGallery from './ProductGallery';

const productImages = [
  'https://dummyimage.com/1000x400/070be6/fff.png',
  'https://dummyimage.com/900x900/bd09e6/fff.png',
  'https://dummyimage.com/600x600/070be6/fff.png',
];

describe('ProductGallery', () => {
  it('Renders Primary Card', () => {
    render(<ProductGallery productImages={productImages} />);
    const imgThumbnailElements = screen.findAllByAltText(
      'product gallery thumbnail',
    );
    expect(imgThumbnailElements).toHaveLength(3);
  });
});

Package.JSON:

{
  "name": "groceries-plus",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint:prettier": "prettier -c '{components,pages,cypress,styles}/**/*.{js,ts,tsx,jsx,css,json}'",
    "lint:fix:prettier": "prettier --write '{components,pages,cypress,styles}/**/*.{js,ts,tsx,jsx,css,json}'",
    "lint:eslint": "eslint",
    "lint:fix:eslint": "eslint --fix",
    "lint": "concurrently \"npm:lint:*(!fix)\"",
    "test": "concurrently \"npm:test:*(!run)\"",
    "test:rtl": "jest --coverage",
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook",
    "generate-types": "contentful-typescript-codegen --output @types/generated/contentful.types.ts",
    "test:cy:run": "cypress run",
    "test:cy:start": "start-server-and-test dev 3000 test:cy:run"
  },
  "dependencies": {
    "@reduxjs/toolkit": "^1.8.5",
    "contentful": "^9.2.4",
    "next": "12.3.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-redux": "^8.0.2",
    "swiper": "^8.4.4"
  },
  "devDependencies": {
    "@babel/core": "^7.19.1",
    "@contentful/rich-text-types": "^15.13.2",
    "@storybook/addon-actions": "^6.5.12",
    "@storybook/addon-essentials": "^6.5.12",
    "@storybook/addon-interactions": "^6.5.12",
    "@storybook/addon-links": "^6.5.12",
    "@storybook/addon-postcss": "^2.0.0",
    "@storybook/builder-webpack5": "^6.5.12",
    "@storybook/cli": "^6.5.12",
    "@storybook/manager-webpack5": "^6.5.12",
    "@storybook/react": "^6.5.12",
    "@storybook/testing-library": "^0.0.13",
    "@tailwindcss/typography": "^0.5.7",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^14.4.3",
    "@types/node": "18.7.18",
    "@types/react": "18.0.20",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "autoprefixer": "^10.4.11",
    "babel-loader": "^8.2.5",
    "concurrently": "^7.4.0",
    "contentful-management": "^10.15.1",
    "contentful-typescript-codegen": "^3.2.3",
    "cypress": "^10.9.0",
    "dotenv": "^16.0.2",
    "eslint": "^8.0.1",
    "eslint-config-next": "12.3.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-config-standard-with-typescript": "^23.0.0",
    "eslint-plugin-cypress": "^2.12.1",
    "eslint-plugin-import": "^2.25.2",
    "eslint-plugin-n": "^15.0.0",
    "eslint-plugin-prettier": "^4.2.1",
    "eslint-plugin-promise": "^6.0.0",
    "eslint-plugin-react": "^7.31.8",
    "eslint-plugin-storybook": "^0.6.4",
    "jest": "^29.0.3",
    "jest-environment-jsdom": "^29.0.3",
    "postcss": "^8.4.16",
    "prettier": "^2.7.1",
    "prettier-plugin-tailwindcss": "^0.1.13",
    "start-server-and-test": "^1.14.0",
    "tailwindcss": "^3.1.8",
    "typescript": "4.8.3",
    "webpack": "^5"
  }
}

jest.config.js:

const nextJest = require('next/jest');

const createJestConfig = nextJest({
  dir: './',
});

/** @type {import('jest').Config} */
const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: 'jest-environment-jsdom',
};

module.exports = createJestConfig(customJestConfig);

The Component and story render fine. enter image description here enter image description here

While running the test I'm getting the following error: enter image description here

Tried so far:

I've tried many things like, adding the transformIgnorePatterns option to jest.config.js but it didn’t fix the error.

  transformIgnorePatterns: [
    '/node_modules/(?![swiper/react/swiper-react.js])',
    '/node_modules/(?![swiper/react/swiper.js])'
  ],

After some more debugging, I modified the test:rtl script to this in package.json:

"test:rtl": "jest --coverage --transformIgnorePatterns \"node_modules/(?!swiper)/\"",

I'm looking for a lifesaver here. Thanks in advance.


Solution

  • I don't think I got there in time to save your life but the best solution I found is to mock all the things related with Swiper, in your case:

    jest.mock("swiper/react", () => ({
      Swiper: ({ children }: { children: React.ReactElement }) => (
        <div>{children}</div>
      ),
      SwiperSlide: ({ children }: { children: React.ReactElement }) => (
        <div>{children}</div>
      ),
      useSwiper: () => ({
        swiper: {
          slideNext: () => {},
        },
      }),
    }));
    
    jest.mock("swiper", () => ({
      default: jest.fn(),
      Thumbs: jest.fn()
    }));