diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a16f856225..007f1fdc2455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [`master`](https://github.com/elastic/eui/tree/master) -No public interface changes since `0.0.52`. +- `EuiComboBox` is now decorated with `data-test-subj` selectors for the search input and clear button +- `EuiComboBox` now gives focus to the search input when the user clicks the clear button, to prevent focus from defaulting to the body ## [`0.0.52`](https://github.com/elastic/eui/tree/v0.0.52) diff --git a/src/components/combo_box/__snapshots__/combo_box.test.js.snap b/src/components/combo_box/__snapshots__/combo_box.test.js.snap index 0d50dc5a6ad7..74ee30af5a61 100644 --- a/src/components/combo_box/__snapshots__/combo_box.test.js.snap +++ b/src/components/combo_box/__snapshots__/combo_box.test.js.snap @@ -3,8 +3,68 @@ exports[`EuiComboBox is rendered 1`] = `
+
+
+
+ +
+
+
+
+ +
+
+
+`; + +exports[`props isClearable=false disallows user from clearing input when no options are selected 1`] = ` +
@@ -14,7 +74,6 @@ exports[`EuiComboBox is rendered 1`] = ` inputRef={[Function]} isListOpen={false} onChange={[Function]} - onClear={[Function]} onClick={[Function]} onCloseListClick={[Function]} onFocus={[Function]} @@ -30,11 +89,9 @@ exports[`EuiComboBox is rendered 1`] = `
`; -exports[`props isClearable is false with selectedOptions 1`] = ` +exports[`props isClearable=false disallows user from clearing input when options are selected 1`] = `
@@ -68,40 +125,9 @@ exports[`props isClearable is false with selectedOptions 1`] = `
`; -exports[`props isClearable is false without selectedOptions 1`] = ` +exports[`props isDisabled is rendered 1`] = `
- -
-`; - -exports[`props isDisabled 1`] = ` -
@@ -133,11 +159,9 @@ exports[`props isDisabled 1`] = `
`; -exports[`props options 1`] = ` +exports[`props options are rendered 1`] = `
@@ -163,11 +187,9 @@ exports[`props options 1`] = `
`; -exports[`props selectedOptions 1`] = ` +exports[`props selectedOptions are rendered 1`] = `
@@ -202,11 +224,9 @@ exports[`props selectedOptions 1`] = `
`; -exports[`props singleSelection 1`] = ` +exports[`props singleSelection is rendered 1`] = `
diff --git a/src/components/combo_box/combo_box.js b/src/components/combo_box/combo_box.js index 94bc1509a5c9..4de12691c19b 100644 --- a/src/components/combo_box/combo_box.js +++ b/src/components/combo_box/combo_box.js @@ -401,6 +401,9 @@ export class EuiComboBox extends Component { clearSelectedOptions = () => { this.props.onChange([]); + // Clicking the clear button will also cause it to disappear. This would result in focus + // shifting unexpectedly to the body element so we set it to the input which is more reasonable, + this.searchInput.focus(); } onComboBoxClick = () => { diff --git a/src/components/combo_box/combo_box.test.js b/src/components/combo_box/combo_box.test.js index f1a0c3cc5d42..63797c23bd7f 100644 --- a/src/components/combo_box/combo_box.test.js +++ b/src/components/combo_box/combo_box.test.js @@ -1,6 +1,7 @@ import React from 'react'; -import { shallow } from 'enzyme'; -import { requiredProps } from '../../test'; +import { shallow, render, mount } from 'enzyme'; +import sinon from 'sinon'; +import { requiredProps, findTestSubject } from '../../test'; import { EuiComboBox } from './combo_box'; @@ -29,7 +30,7 @@ const options = [{ describe('EuiComboBox', () => { test('is rendered', () => { - const component = shallow( + const component = render( ); @@ -38,49 +39,45 @@ describe('EuiComboBox', () => { }); describe('props', () => { - test('options', () => { + test('options are rendered', () => { + // NOTE: It's tough to test this because the dropdown containing the options opens up in + // a portal. const component = shallow( - + ); expect(component).toMatchSnapshot(); }); - test('selectedOptions', () => { + test('selectedOptions are rendered', () => { const component = shallow( ); expect(component).toMatchSnapshot(); }); - describe('isClearable is false', () => { - test('without selectedOptions', () => { + describe('isClearable=false disallows user from clearing input', () => { + test('when no options are selected', () => { const component = shallow( ); expect(component).toMatchSnapshot(); }); - test('with selectedOptions', () => { + test('when options are selected', () => { const component = shallow( ); @@ -88,29 +85,59 @@ describe('props', () => { }); }); - test('singleSelection', () => { + test('singleSelection is rendered', () => { const component = shallow( ); expect(component).toMatchSnapshot(); }); - test('isDisabled', () => { + test('isDisabled is rendered', () => { const component = shallow( ); expect(component).toMatchSnapshot(); }); }); + +describe('behavior', () => { + describe('clear button', () => { + test('calls onChange callback with empty array', () => { + const onChangeHandler = sinon.spy(); + const component = mount( + + ); + + findTestSubject(component, 'comboBoxClearButton').simulate('click'); + sinon.assert.calledOnce(onChangeHandler); + sinon.assert.calledWith(onChangeHandler, []); + }); + + test('focuses the input', () => { + const component = mount( + {}} + /> + ); + + findTestSubject(component, 'comboBoxClearButton').simulate('click'); + expect(findTestSubject(component, 'comboBoxSearchInput').getDOMNode()).toBe(document.activeElement); + }); + }); +}); diff --git a/src/components/combo_box/combo_box_input/combo_box_input.js b/src/components/combo_box/combo_box_input/combo_box_input.js index 80612d61843e..74a9add96828 100644 --- a/src/components/combo_box/combo_box_input/combo_box_input.js +++ b/src/components/combo_box/combo_box_input/combo_box_input.js @@ -152,6 +152,7 @@ export class EuiComboBoxInput extends Component { if (!isDisabled && onClear && hasSelectedOptions) { clickProps.clear = { onClick: onClear, + 'data-test-subj': 'comboBoxClearButton', }; } @@ -188,6 +189,7 @@ export class EuiComboBoxInput extends Component { ref={autoSizeInputRef} inputRef={inputRef} disabled={isDisabled} + data-test-subj="comboBoxSearchInput" /> {removeOptionMessage}