diff --git a/src/Dropdown.tsx b/src/Dropdown.tsx index 42f2b01..b9f74c1 100644 --- a/src/Dropdown.tsx +++ b/src/Dropdown.tsx @@ -50,7 +50,7 @@ export const Dropdown: React.FC = ({ findSelected(options, value, matcher), ); const [isOpen, setIsOpen] = useState(false); - const dropdownNode = useRef(); + const dropdownNode = useRef(null); const handleOpenStateEvents = (dropdownIsOpen, closedBySelection = false) => { if (dropdownIsOpen && typeof onOpen === 'function') { @@ -97,10 +97,19 @@ export const Dropdown: React.FC = ({ } }; + const isDropdownNodeEvent = (e?: React.SyntheticEvent) => { + const { current } = dropdownNode; + + return Boolean(e && current && current.contains(e.target as Node)); + }; + const fireChangeEvent = ( newSelectedState: RenderItem, e?: React.SyntheticEvent, ) => { + if (!isDropdownNodeEvent(e)) { + return; + } if (onSelect) { onSelect(newSelectedState.option, e); } diff --git a/src/__tests__/index.spec.tsx b/src/__tests__/index.spec.tsx index e064b57..7492e6e 100644 --- a/src/__tests__/index.spec.tsx +++ b/src/__tests__/index.spec.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { fireEvent, render } from '@testing-library/react'; import { Dropdown as ReactDropdownNow } from '..'; @@ -201,6 +201,150 @@ describe('Dropdown', () => { unmount(); }); + it('should not call onChange when component is unmounted', () => { + const onOpen = jest.fn(); + const onClose = jest.fn(); + const onChange = jest.fn(); + const { unmount, getByTestId, getAllByTestId } = render( + , + ); + + const dropdownControl = getByTestId('dropdown-control'); + const dropdownRoot = getByTestId('dropdown-root'); + + fireEvent.mouseDown(dropdownControl); + + expect(onOpen).toHaveBeenCalledTimes(1); + expect(dropdownRoot.classList.contains('is-open')).toBe(true); + + const dropdownOptions = getAllByTestId('dropdown-option'); + fireEvent.mouseDown(dropdownOptions[2]); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onClose).toHaveBeenCalledTimes(1); + + unmount(); + + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('should call onChange only when change originates at self', () => { + const onChangeDropdownOne = jest.fn(); + const onChangeDropdownTwo = jest.fn(); + + const ContainerTwoDropdowns = () => { + const [v, setV] = useState('one'); + + return ( +
+ + { + onChangeDropdownTwo(o); + setV(String(o.value)); + }} + /> +
+ ); + }; + + const { unmount, getByTestId, getAllByTestId } = render( + , + ); + + const dropdownControls = getAllByTestId('dropdown-control'); + + fireEvent.mouseDown(dropdownControls[1]); + fireEvent.mouseDown(getAllByTestId('dropdown-option')[2]); + + expect(onChangeDropdownTwo).toHaveBeenCalledTimes(1); + expect(onChangeDropdownOne).toHaveBeenCalledTimes(0); + + unmount(); + }); + + it('should call onSelect', () => { + const onOpen = jest.fn(); + const onClose = jest.fn(); + const onChange = jest.fn(); + const onSelect = jest.fn(); + const { unmount, getByTestId, getAllByTestId } = render( + , + ); + + const dropdownControl = getByTestId('dropdown-control'); + const dropdownRoot = getByTestId('dropdown-root'); + + fireEvent.mouseDown(dropdownControl); + + expect(onOpen).toHaveBeenCalledTimes(1); + expect(dropdownRoot.classList.contains('is-open')).toBe(true); + + fireEvent.mouseDown(getAllByTestId('dropdown-option')[2]); + expect(onSelect).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onClose).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('should call onSelect, even same item is selected', () => { + const onOpen = jest.fn(); + const onClose = jest.fn(); + const onChange = jest.fn(); + const onSelect = jest.fn(); + const { unmount, getByTestId, getAllByTestId } = render( + , + ); + + const dropdownControl = getByTestId('dropdown-control'); + const dropdownRoot = getByTestId('dropdown-root'); + + fireEvent.mouseDown(dropdownControl); + + expect(onOpen).toHaveBeenCalledTimes(1); + expect(dropdownRoot.classList.contains('is-open')).toBe(true); + + fireEvent.mouseDown(getAllByTestId('dropdown-option')[2]); + expect(onSelect).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onClose).toHaveBeenCalledTimes(1); + + fireEvent.mouseDown(dropdownControl); + fireEvent.mouseDown(getAllByTestId('dropdown-option')[2]); + + expect(onSelect).toHaveBeenCalledTimes(2); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onClose).toHaveBeenCalledTimes(2); + + unmount(); + }); + it('should clear value state', () => { const { unmount, getByTestId } = render(