Skip to content

Commit

Permalink
[Uptime] [User experience] prevent search input focus loss (elastic#1…
Browse files Browse the repository at this point in the history
…06601) (elastic#106614)

* user experience - preserve focus on search input when clicked

* dismiss popover on escape and input blur

* adjust types

Co-authored-by: Dominique Clarke <[email protected]>
  • Loading branch information
kibanamachine and dominiqueclarke authored Jul 23, 2021
1 parent ec53f78 commit 88eb5cd
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,49 @@
* 2.0.
*/

import React from 'react';
import React, { useState } from 'react';
import {
fireEvent,
waitFor,
waitForElementToBeRemoved,
screen,
} from '@testing-library/react';
import { createMemoryHistory } from 'history';
import * as fetcherHook from '../../../../../hooks/use_fetcher';
import { SelectableUrlList } from './SelectableUrlList';
import { render } from '../../utils/test_helper';
import { I18LABELS } from '../../translations';

describe('SelectableUrlList', () => {
it('it uses search term value from url', () => {
jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({
data: {},
status: fetcherHook.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});
jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({
data: {},
status: fetcherHook.FETCH_STATUS.SUCCESS,
refetch: jest.fn(),
});

const customHistory = createMemoryHistory({
initialEntries: ['/?searchTerm=blog'],
});
const customHistory = createMemoryHistory({
initialEntries: ['/?searchTerm=blog'],
});

function WrappedComponent() {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
return (
<SelectableUrlList
initialValue={'blog'}
loading={false}
data={{ items: [], total: 0 }}
onChange={jest.fn()}
searchValue={'blog'}
onInputChange={jest.fn()}
onTermChange={jest.fn()}
popoverIsOpen={Boolean(isPopoverOpen)}
setPopoverIsOpen={setIsPopoverOpen}
onApply={jest.fn()}
/>
);
}

it('it uses search term value from url', () => {
const { getByDisplayValue } = render(
<SelectableUrlList
initialValue={'blog'}
Expand All @@ -40,4 +65,57 @@ describe('SelectableUrlList', () => {
);
expect(getByDisplayValue('blog')).toBeInTheDocument();
});

it('maintains focus on search input field', () => {
const { getByLabelText } = render(
<SelectableUrlList
initialValue={'blog'}
loading={false}
data={{ items: [], total: 0 }}
onChange={jest.fn()}
searchValue={'blog'}
onInputChange={jest.fn()}
onTermChange={jest.fn()}
popoverIsOpen={false}
setPopoverIsOpen={jest.fn()}
onApply={jest.fn()}
/>,
{ customHistory }
);

const input = getByLabelText(I18LABELS.filterByUrl);
fireEvent.click(input);

expect(document.activeElement).toBe(input);
});

it('hides popover on escape', async () => {
const {
getByText,
getByLabelText,
queryByText,
} = render(<WrappedComponent />, { customHistory });

const input = getByLabelText(I18LABELS.filterByUrl);
fireEvent.click(input);

// wait for title of popover to be present
await waitFor(() => {
expect(getByText(I18LABELS.getSearchResultsLabel(0))).toBeInTheDocument();
screen.debug();
});

// escape key
fireEvent.keyDown(input, {
key: 'Escape',
code: 'Escape',
keyCode: 27,
charCode: 27,
});

// wait for title of popover to be removed
await waitForElementToBeRemoved(() =>
queryByText(I18LABELS.getSearchResultsLabel(0))
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ interface Props {
searchValue: string;
popoverIsOpen: boolean;
initialValue?: string;
setPopoverIsOpen: React.Dispatch<SetStateAction<boolean | undefined>>;
setPopoverIsOpen: React.Dispatch<SetStateAction<boolean>>;
}

export function SelectableUrlList({
Expand All @@ -93,6 +93,8 @@ export function SelectableUrlList({

const titleRef = useRef<HTMLDivElement>(null);

const formattedOptions = formatOptions(data.items ?? []);

const onEnterKey = (evt: KeyboardEvent<HTMLInputElement>) => {
if (evt.key.toLowerCase() === 'enter') {
onTermChange();
Expand All @@ -104,27 +106,30 @@ export function SelectableUrlList({
}
};

// @ts-ignore - not sure, why it's not working
useEvent('keydown', onEnterKey, searchRef);

const onInputClick = (e: React.MouseEvent<HTMLInputElement>) => {
setPopoverIsOpen(true);
if (searchRef) {
searchRef.focus();
}
};

const onSearchInput = (e: React.FormEvent<HTMLInputElement>) => {
onInputChange(e);
setPopoverIsOpen(true);
};

const formattedOptions = formatOptions(data.items ?? []);

const closePopover = () => {
setPopoverIsOpen(false);
if (searchRef) {
searchRef.blur();
}
};

// @ts-ignore - not sure, why it's not working
useEvent('keydown', onEnterKey, searchRef);
useEvent('escape', () => setPopoverIsOpen(false), searchRef);
useEvent('blur', () => setPopoverIsOpen(false), searchRef);

useEffect(() => {
if (searchRef && initialValue) {
searchRef.value = initialValue;
Expand Down Expand Up @@ -189,6 +194,7 @@ export function SelectableUrlList({
onInput: onSearchInput,
inputRef: setSearchRef,
placeholder: I18LABELS.filterByUrl,
'aria-label': I18LABELS.filterByUrl,
}}
listProps={{
rowHeight: 68,
Expand All @@ -210,6 +216,7 @@ export function SelectableUrlList({
closePopover={closePopover}
style={{ minWidth: 400 }}
anchorPosition="downLeft"
ownFocus={false}
>
<div
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function URLSearch({

const { searchTerm, percentile } = urlParams;

const [popoverIsOpen, setPopoverIsOpen] = useState<boolean | undefined>();
const [popoverIsOpen, setPopoverIsOpen] = useState<boolean>(false);

const [searchValue, setSearchValue] = useState(searchTerm ?? '');

Expand Down

0 comments on commit 88eb5cd

Please sign in to comment.