testing-library / react-testing-library

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.
https://testing-library.com/react
MIT License
18.84k stars 1.09k forks source link

Error test Component with setTimeout and vitest #1334

Closed kyrylsherstobaiev closed 1 month ago

kyrylsherstobaiev commented 1 month ago

Package.json

  "devDependencies": {
    "@testing-library/jest-dom": "^6.4.5",
    "@testing-library/react": "^15.0.7",
    "@testing-library/user-event": "^14.5.2",
    "@types/react": "^18.3.1",
    "@types/react-dom": "^18.3.0",
    "@vitejs/plugin-react": "^4.2.1",
    "jsdom": "^24.0.0",
    "prettier": "3.2.5",
    "sass": "^1.76.0",
    "vite": "^5.2.11",
    "vitest": "^1.6.0"
  }

JSX files:

import { ToastCustom } from "./ToastCustom.jsx";

import React, { useRef } from "react";
import PropTypes from "prop-types";

function ContainerHandleToast({ timeout }) {
  const toastRef = useRef(null);

  const handleToast = () => {
    toastRef.current.showToast("Hello");
  };

  return (
    <div className="container-toast" data-testid="container-toast">
      <ToastCustom ref={toastRef} timeout={timeout} data-testid="toast-element" />
      <button onClick={handleToast}>Show Toast</button>
    </div>
  );
}

ContainerHandleToast.propTypes = {
  timeout: PropTypes.number,
};
export { ContainerHandleToast };
import React, { forwardRef, useImperativeHandle, useState } from "react";
import PropTypes from "prop-types";
import classNames from "classnames";

const ToastCustom = forwardRef(
  ({ timeout = 2000, className, ...props }, ref) => {
    const [show, setShow] = useState(false);
    const [messageText, setMessage] = useState("");
    const classes = classNames("toast-message", className, { show: show });

    useImperativeHandle(ref, () => ({
      showToast(message = "") {
        setShow(true);
        setMessage(message);
        setTimeout(() => {
          setShow(false);
        }, timeout);
      },
    }));

    return (
      <div className={classes} {...props}>
        {messageText}
      </div>
    );
  },
);

ToastCustom.propTypes = {
  timeout: PropTypes.number,
  className: PropTypes.string,
};
export { ToastCustom };

Test.jsx

import { describe, it, expect } from "vitest";
import {act, render, screen, waitFor} from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";

import { ContainerHandleToast } from "./ContainerHandleToast";

describe("<ToastCustom/>", () => {
  it("renders without crash", () => {
    render(<ContainerHandleToast />);
    const containerToast = screen.getByTestId("container-toast", {});
    const toastElement = screen.getByTestId("toast-element", {});
    expect(containerToast).toBeInTheDocument();
    expect(toastElement).toBeInTheDocument();
  });
  it("should be appeared toast", async () => {
    vi.useFakeTimers();
    const user = userEvent.setup();
    render(<ContainerHandleToast />);
    await user.click(screen.getByRole("button", { name: /show toast/i }));
    expect(screen.getByTestId("toast-element", {})).toHaveClass("show");

    await waitFor(async () => {
      await vi.advanceTimersByTime(2000); // ---- CATCH BAG -----
    });
   expect(screen.getByTestId("toast-element", {})).not.toHaveClass("show");

  });
});

SCSS

  .toast-message {

    transform: translateY(-500px);
    z-index: 100;

    &.show {
      transform: translateY(0px);
    }

Problem description:

It catchs the error at this lines!

    await waitFor(async () => {
      await vi.advanceTimersByTime(2000); // ---- CATCH BAG -----
    });
    expect(screen.getByTestId("toast-element", {})).not.toHaveClass("show");