Search code examples
reactjsjestjsenzymereact-testing-libraryreact-hooks-testing-library

can't build test to useOnClickOutside custom hook


I build a custom hook and I want to build test from it and I can't figure out where to start I would like you help to explain to me few things:

1.how I catch mousedown on the test 2. how I use the useRef and assigin it to current value It will be very helpful if you could help me and show me a code because I am sit sometimes on it

below this is the custom hook and the code I implemented the custom hook in thanks in advance 😁

 import { useEffect } from 'react';

function useOnClickOutside(ref, callback) {
  useEffect(
    () => {
      const listener = (event) => {
        // Do nothing if clicking ref's element or descendent elements
        if (!ref.current || ref.current.contains(event.target)) {
          return;
        }

        callback(event);
      };

      document.addEventListener('mousedown', listener);
      document.addEventListener('touchstart', listener);

      return () => {
        document.removeEventListener('mousedown', listener);
        document.removeEventListener('touchstart', listener);
      };
    },
    [ref, callback],
  );
}

export default useOnClickOutside;

and this is the component that use it:

import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import styles from './OverlayDialog.module.css';
import  useOnClickOutside from '../../CustomeHooks/useOnClickOutside/useOnClickOutside';
const OverlayDialog = (props) => {
    const wrapperRef = useRef(null);
    useEffect(() => {
        window.addEventListener("mousedown", handleClickOutside);
        return () => {
            window.removeEventListener("mousedown", handleClickOutside);
        };
    });
    const handleClickOutside = event => {
        const { current: wrap } = wrapperRef;
        if (wrap && !wrap.contains(event.target)) {
            props.onClose(false);
        }
    };

   useOnClickOutside( wrapperRef,()=>props.onClose(false))

    return ReactDOM.createPortal(
        <div className={styles.Dialog}>
            <div className={styles.InnerDialog} tabIndex={1}>
                <div className={styles.DialogCard} tabIndex={-1}>
                    <div className={props.className ? props.className : styles.DialogContent} ref={wrapperRef}>

                        {props.children}
                    </div>
                </div>
            </div>
        </div>,
        document.getElementById('OverlayDialog')
    )
}

export default OverlayDialog;

Solution

  • I found good blog post for your case. There's information how to test onClickOutside https://webman.pro/blog/how-to-detect-and-test-click-outside-in-react/ I looked on your component and was curios how it actually works) So if you add live example of your code I'll can give you more help)

    update I added test id for content in your component) component:

    import React, { useRef } from "react";
    import ReactDOM from "react-dom";
    import styles from "./OverlayDialog.module.css";
    import useOnClickOutside from "./useOnClickOutside";
    const OverlayDialog = (props) => {
      const wrapperRef = useRef(null);
    
      useOnClickOutside(wrapperRef, () => props.onClose());
    
      return ReactDOM.createPortal(
        <div className={styles.Dialog}>
          <div className={styles.InnerDialog} tabIndex={1}>
            <div className={styles.DialogCard} tabIndex={-1}>
              <div
                className={props.className ? props.className : styles.DialogContent}
                ref={wrapperRef}
                data-testid="content"
              >
                {props.children}
              </div>
            </div>
          </div>
        </div>,
        document.getElementById("OverlayDialog")
      );
    };
    
    export default OverlayDialog;
    

    test:

    import React from 'react';
    import { render, screen, fireEvent } from '@testing-library/react';
    import OverlayDialog from "./OverlayDialog";
    
    test('renders learn react link', () => {
      const onClose = jest.fn();
      const modalRoot = document.createElement('div');
      modalRoot.setAttribute('id', 'OverlayDialog');
      const body = document.querySelector('body');
      body.appendChild(modalRoot);
      render(
          <OverlayDialog onClose={onClose}>
            content
          </OverlayDialog>
      );
      /*checking that if we click on content nothing happens*/
      expect(screen.queryByTestId('content')).toBeInTheDocument();
      fireEvent(screen.queryByTestId('content'), new MouseEvent('mousedown', {
        bubbles: true,
        cancelable: true,
      }));
      expect(onClose).not.toBeCalled();
    
      /*checking that if we click outside we'll fire onClose*/
      fireEvent(body, new MouseEvent('mousedown', {
        bubbles: true,
        cancelable: true,
      }));
      expect(onClose).toBeCalled();
    });