diff --git a/src/components/dropdown/dropdown.spec.tsx b/src/components/dropdown/dropdown.spec.tsx new file mode 100644 index 00000000..fd60e98a --- /dev/null +++ b/src/components/dropdown/dropdown.spec.tsx @@ -0,0 +1,70 @@ +import {act} from 'react-dom/test-utils'; +import {mount, ReactWrapper} from 'enzyme'; +import * as React from 'react'; + +import {DropDown} from './dropdown'; + +describe('DropDown', () => { + let wrapper: ReactWrapper | null; + + afterEach(() => { + if (wrapper) { + wrapper.unmount(); + wrapper = null; + } + }); + + const renderDropdown = (onSelect: () => void) => mount( + } isMenu={true}> + + , + ); + + it('selects the first item when pressing Enter inside an open dropdown', async () => { + const onSelect = jest.fn(); + wrapper = renderDropdown(onSelect); + + await act(async () => { + wrapper!.find('.argo-dropdown__anchor').simulate('click', {stopPropagation: () => {}}); + await Promise.resolve(); + }); + wrapper.update(); + + const preventDefault = jest.fn(); + const event: any = {key: 'Enter', target: wrapper.find('.argo-dropdown__anchor').getDOMNode(), preventDefault}; + + (wrapper.instance() as any).selectTopResult(event); + + expect(onSelect).toHaveBeenCalledTimes(1); + expect(preventDefault).toHaveBeenCalled(); + }); + + it('ignores Enter when the dropdown is closed or when the target is outside', async () => { + const onSelect = jest.fn(); + wrapper = renderDropdown(onSelect); + + const closedEvent: any = {key: 'Enter', target: wrapper.find('.argo-dropdown__anchor').getDOMNode(), preventDefault: jest.fn()}; + + (wrapper.instance() as any).selectTopResult(closedEvent); + + expect(onSelect).not.toHaveBeenCalled(); + + await act(async () => { + wrapper!.find('.argo-dropdown__anchor').simulate('click', {stopPropagation: () => {}}); + await Promise.resolve(); + }); + wrapper.update(); + + const outside = document.createElement('div'); + document.body.appendChild(outside); + const outsideEvent: any = {key: 'Enter', target: outside, preventDefault: jest.fn()}; + + (wrapper.instance() as any).selectTopResult(outsideEvent); + + expect(onSelect).not.toHaveBeenCalled(); + outside.remove(); + }); +}); diff --git a/src/components/dropdown/dropdown.tsx b/src/components/dropdown/dropdown.tsx index 4f447385..89966ce4 100644 --- a/src/components/dropdown/dropdown.tsx +++ b/src/components/dropdown/dropdown.tsx @@ -72,6 +72,8 @@ export class DropDown extends React.Component { if (this.state.opened && this.content && this.el) { this.setState(this.refreshState()); } + }), fromEvent(document, 'keydown').pipe(filter((event) => event.key === 'Enter' || event.keyCode === 13)).subscribe((event) => { + this.selectTopResult(event); })]; } @@ -87,6 +89,21 @@ export class DropDown extends React.Component { } } + private selectTopResult(event: KeyboardEvent) { + if (!this.state.opened || !this.content || !this.el) { + return; + } + const target = event.target as Node; + if (target && !this.el.contains(target) && !this.content.contains(target)) { + return; + } + const firstItem = this.content.querySelector('li') as HTMLElement; + if (firstItem) { + event.preventDefault(); + firstItem.click(); + } + } + private refreshState() { const anchor = this.el.querySelector('.argo-dropdown__anchor') as HTMLElement; const {top, left} = anchor.getBoundingClientRect();