diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx index 1c2e64735ca16..a204c1ec590a4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { MouseEventHandler } from 'react'; +import React from 'react'; import { shallow, mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { EuiPopover, EuiLink } from '@elastic/eui'; @@ -20,19 +20,6 @@ jest.mock('.', () => ({ defaultLabel: 'label', })); -const defaultProps = { - filter: { - input: { query: 'bytes >= 1', language: 'kuery' }, - label: 'More than one', - id: '1', - }, - setFilter: jest.fn(), - indexPattern: createMockedIndexPattern(), - Button: ({ onClick }: { onClick: MouseEventHandler }) => ( - trigger - ), - initiallyOpen: true, -}; jest.mock('../../../../../../../../src/plugins/data/public', () => ({ QueryStringInput: () => { return 'QueryStringInput'; @@ -40,6 +27,67 @@ jest.mock('../../../../../../../../src/plugins/data/public', () => ({ })); describe('filter popover', () => { + let defaultProps: Parameters[0]; + let mockOnClick: jest.Mock; + + beforeEach(() => { + mockOnClick = jest.fn(); + + defaultProps = { + filter: { + input: { query: 'bytes >= 1', language: 'kuery' }, + label: 'More than one', + id: '1', + }, + setFilter: jest.fn(), + indexPattern: createMockedIndexPattern(), + Button: () => trigger, + isOpen: true, + triggerClose: () => {}, + }; + }); + + describe('interactions', () => { + it('should open/close according to isOpen', () => { + const instance = mount(); + + expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true); + + instance.setProps({ ...defaultProps, isOpen: false }); + instance.update(); + + expect(instance.find(EuiPopover).prop('isOpen')).toEqual(false); + }); + + it('should report click event', () => { + const instance = mount(); + + expect(mockOnClick).not.toHaveBeenCalled(); + + instance.find(EuiPopover).find('button').simulate('click', {}); + + expect(mockOnClick).toHaveBeenCalledTimes(1); + }); + + it('should trigger close', () => { + const props = { ...defaultProps, triggerClose: jest.fn() }; + const instance = mount(); + expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true); + + // Trigger from EuiPopover + act(() => { + instance.find(EuiPopover).prop('closePopover')!(); + }); + expect(props.triggerClose).toHaveBeenCalledTimes(1); + + // Trigger from submit + act(() => { + instance.find(LabelInput).prop('onSubmit')!(); + }); + expect(props.triggerClose).toHaveBeenCalledTimes(2); + }); + }); + it('passes correct props to QueryStringInput', () => { const instance = mount(); instance.update(); @@ -52,16 +100,7 @@ describe('filter popover', () => { }) ); }); - it('should be open if is open by creation', () => { - const instance = mount(); - instance.update(); - expect(instance.find(EuiPopover).prop('isOpen')).toEqual(true); - act(() => { - instance.find(EuiPopover).prop('closePopover')!(); - }); - instance.update(); - expect(instance.find(EuiPopover).prop('isOpen')).toEqual(false); - }); + it('should call setFilter when modifying QueryInput', () => { const setFilter = jest.fn(); const instance = shallow(); @@ -78,6 +117,7 @@ describe('filter popover', () => { id: '1', }); }); + it('should call setFilter when modifying LabelInput', () => { const setFilter = jest.fn(); const instance = shallow(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx index bfb0cffece57c..cf95ae2dfd0e8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx @@ -7,7 +7,7 @@ import './filter_popover.scss'; -import React, { MouseEventHandler, useEffect, useState } from 'react'; +import React from 'react'; import { EuiPopover, EuiSpacer } from '@elastic/eui'; import { FilterValue, defaultLabel, isQueryValid } from '.'; import { IndexPattern } from '../../../types'; @@ -20,28 +20,40 @@ export const FilterPopover = ({ setFilter, indexPattern, Button, - initiallyOpen, + isOpen, + triggerClose, }: { filter: FilterValue; setFilter: Function; indexPattern: IndexPattern; - Button: React.FunctionComponent<{ onClick: MouseEventHandler }>; - initiallyOpen: boolean; + Button: React.FunctionComponent; + isOpen: boolean; + triggerClose: () => void; }) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); const inputRef = React.useRef(); - // set popover open on start to work around EUI bug - useEffect(() => { - setIsPopoverOpen(initiallyOpen); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + // The following code is to prevent an keypress + // from propagating. + // + // TODO - It looks like EUI should be handling this + // (see https://github.com/elastic/eui/commit/ad97583b0d644690379f72c7a20879cfadb16e7a) + const popoverRef = React.useRef(null); + let panelElement: HTMLDivElement; + const panelRefCallback = (element: HTMLDivElement) => { + const listener = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + event.stopPropagation(); + panelElement.removeEventListener('keydown', listener); + popoverRef.current?.closePopover(); + } + }; - const closePopover = () => { - if (isPopoverOpen) { - setIsPopoverOpen(false); + if (element) { + panelElement = element; + panelElement.addEventListener('keydown', listener); } }; + // End handling code const setFilterLabel = (label: string) => setFilter({ ...filter, label }); const setFilterQuery = (input: Query) => setFilter({ ...filter, input }); @@ -58,19 +70,15 @@ export const FilterPopover = ({ return ( closePopover()} - button={ -