Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
fix: no onSelect event when same item selected (#102)
Browse files Browse the repository at this point in the history
* fix: no onSelect event when same item selected

* fix: do not emit onChange unless self-originated event

* chore: make warning go away, import storybook/addon-docs only
  • Loading branch information
iambumblehead authored May 24, 2021
1 parent 3b0b733 commit 2bf0333
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 3 deletions.
11 changes: 10 additions & 1 deletion src/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const Dropdown: React.FC<ReactDropdownProps> = ({
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') {
Expand Down Expand Up @@ -97,10 +97,19 @@ export const Dropdown: React.FC<ReactDropdownProps> = ({
}
};

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);
}
Expand Down
146 changes: 145 additions & 1 deletion src/__tests__/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -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 '..';
Expand Down Expand Up @@ -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(
<ReactDropdownNow
options={['one', 'two', 'three']}
value="one"
onOpen={onOpen}
onClose={onClose}
onChange={onChange}
/>,
);

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 (
<div>
<ReactDropdownNow
options={['one', 'two', 'three']}
value={v}
onChange={onChangeDropdownOne}
/>
<ReactDropdownNow
options={['one', 'two', 'three']}
value={v}
onChange={(o) => {
onChangeDropdownTwo(o);
setV(String(o.value));
}}
/>
</div>
);
};

const { unmount, getByTestId, getAllByTestId } = render(
<ContainerTwoDropdowns />,
);

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(
<ReactDropdownNow
options={['one', 'two', 'three']}
value="one"
onOpen={onOpen}
onClose={onClose}
onChange={onChange}
onSelect={onSelect}
/>,
);

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(
<ReactDropdownNow
options={['one', 'two', 'three']}
value="one"
onOpen={onOpen}
onClose={onClose}
onChange={onChange}
onSelect={onSelect}
/>,
);

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(
<ReactDropdownNow
Expand Down
2 changes: 1 addition & 1 deletion src/stories/Introduction.stories.mdx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Meta } from '@storybook/addon-docs/blocks';
import { Meta } from '@storybook/addon-docs';

<Meta title="Docs/Introduction" />

Expand Down

0 comments on commit 2bf0333

Please sign in to comment.