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}