kentcdodds / ama

Ask me anything!
https://github.com/kentcdodds/ama/issues?q=is%3Aissue+is%3Aclosed
685 stars 75 forks source link

How to test "outsideClick" using React testing library? #832

Closed SMH110 closed 4 years ago

SMH110 commented 4 years ago

Hi, I have a dropdown component which should close when a user clicks outside the dropdown. Here is the implementation:

class WithOutsideClick extends React.Component<any> {
        private wrapperRef = React.createRef<HTMLDivElement>();
        constructor(props) {
            super(props);
            this.handleOutSideClick = this.handleOutSideClick.bind(this);
        }

        componentDidMount() {
            document.addEventListener("mousedown", this.handleOutSideClick);
        }

        componentWillUnmount() {
            document.removeEventListener("mousedown", this.handleOutSideClick);
        }

        private handleOutSideClick(event) {
            if (
                this.wrapperRef.current &&
                !this.wrapperRef.current.contains(event.target) &&
                this.props.allowedId &&
                this.props.allowedId !== event.target.id
            ) {
                this.props.onOutsideClick();
            }
        }

        render() {
            const { children }= this.props;
            return (
                <div  ref={this.wrapperRef}>
                    { children }
                </div>
            );
        }
    };

Here is my test

describe("Testing a component which has an input field, when you focus the input field a dropdown opens with some options - Similar to when trying to enter a date in Google maps ", () => {
    /* 

        define setup function ...etc
    */    
  it("Close the dropdown - outside click", () => {
    // Arrange
    const dropdownLabel = `dropdown`;
    const inputLabel = "a random label"

    const map: any = {};
    document.addEventListener = jest.fn((event, cb) => {
      map[event] = cb;
    });

    // setup is a function return a rendered react component which has input field and dropdown 
    const { queryByLabelText } = setup();
    const inputControl = queryByLabelText(
        inputLabel
    ) as HTMLInputElement;

    // firing focus event on the input field to open the dropdown
    map.focus({ target: inputControl });
    let dropdownElement = queryByLabelText(dropdownLabel);
    expect(dropdownElement).toBeInTheDocument();

    // Act
    const randomElement = queryByLabelText("an element in the DOM not contained by the dropdown");
    map.mousedown({ target: randomElement });

    // Assert
    dropdownElement = queryByLabelText(dropdownLabel);
    expect(dropdownElement).toBeNull();
  });
});

I found this solution on stackoverflow


 const map: any = {};
    document.addEventListener = jest.fn((event, cb) => {
      map[event] = cb;
    });

But, this made the test flaky!

What is the best way to implement outsideclick and how to test it?

Thanks

kentcdodds commented 4 years ago

Hi @SMH110,

I answered this during office hours today: https://www.youtube.com/watch?v=ph_dQoIKzoY

Here's an example test: https://github.com/downshift-js/downshift/blob/23f37157dbd4f27433ac92406b7089c15d056bcd/src/__tests__/downshift.lifecycle.js