0

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;
TalOrlanczyk
  • 1,205
  • 7
  • 21

2 Answers2

3

Example to test useOnClickOutside in isolation

import { createRef } from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { fireEvent, render, screen } from '@testing-library/react';

import useOnClickOutside from './useOnClickOutside';

describe('useOnClickOutside', () => {
  it('calls handler when click is outside element', () => {
    // Arrange
    const handler = jest.fn();
    const ref = createRef<HTMLDivElement>();
    render(<div ref={ref}></div>);

    // Act
    renderHook(() => useOnClickOutside(ref, handler));
    fireEvent.click(document);

    // Assert
    expect(handler).toBeCalledTimes(1);
  });

  it(`doesn't calls handler when click is within element`, () => {
    // Arrange
    const handler = jest.fn();
    const ref = createRef<HTMLDivElement>();
    render(<div ref={ref} data-testid="element-testid"></div>);

    // Act
    renderHook(() => useOnClickOutside(ref, handler));
    fireEvent.click(screen.getByTestId('element-testid'));

    //  Assert
    expect(handler).not.toBeCalled();
  });
});

Harry Singh
  • 51
  • 1
  • 3
1

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();
});

Boykov
  • 374
  • 1
  • 6
  • https://codesandbox.io/s/zealous-hofstadter-5dcht?file=/src/App.js this his a small demo – TalOrlanczyk Aug 09 '20 at 23:51
  • I updated my answer. Also i'm not familiar with @testing-library. Usually i use jest/enzyme without such wrappers. So i think it could be done without `import { screen } from '@testing-library/react'; ` but i don't know how) – Boykov Aug 10 '20 at 14:12