Skip to content

Commit

Permalink
fix: [Search:Search Applications page]Popover for Create button is in…
Browse files Browse the repository at this point in the history
…accessible via keyboard (elastic#201193)

Closes: elastic#199760 
Popovers, dialogs which are accessible with mouse, should also be
accessible with keyboard. Otherwise users using only keyboard will miss
the information present in popover, dialog.

Closes: elastic#199749
User reaches the same button two times when navigating using only
keyboard and it can get confusing. Better for element to get focus only
one time when navigating in sequence from one element to another and for
the user only to hear one announcement of the element.

## What was changed: 
1. `CreateSearchApplicationButton` component: 
    - `EuiPopover` was replaced to more a11y-friendly `EuiToolTip`
    - extra `div` element with `tabindex` was removed. 

## Screen


https://github.com/user-attachments/assets/fbb62841-6f2f-45d0-bee3-39a11a4fc777
  • Loading branch information
alexwizp authored Nov 22, 2024
1 parent 37a0861 commit 1b742e7
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 126 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { type ReactNode } from 'react';

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { waitForEuiToolTipVisible } from '@elastic/eui/lib/test/rtl';

import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';

import { CreateSearchApplicationButton } from './search_applications_list';

function Container({ children }: { children?: ReactNode }) {
return <IntlProvider locale="en">{children}</IntlProvider>;
}

describe('CreateSearchApplicationButton', () => {
test('disabled={false}', async () => {
render(
<Container>
<CreateSearchApplicationButton disabled={false} />
</Container>
);

await userEvent.hover(
await screen.findByTestId('enterprise-search-search-applications-creation-button')
);

await waitForEuiToolTipVisible();

expect(
await screen.findByTestId('create-search-application-button-popover-content')
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { setMockActions, setMockValues } from '../../../__mocks__/kea_logic';

import React from 'react';

import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers';
import { shallowWithIntl } from '@kbn/test-jest-helpers';

import { Status } from '../../../../../common/types/api';

Expand Down Expand Up @@ -116,80 +116,3 @@ describe('SearchApplicationsList', () => {
expect(wrapper.find(LicensingCallout)).toHaveLength(0);
});
});

describe('CreateSearchApplicationButton', () => {
describe('disabled={true}', () => {
it('renders a disabled button that shows a popover when hovered', () => {
const wrapper = mountWithIntl(<CreateSearchApplicationButton disabled />);

const button = wrapper.find(
'button[data-test-subj="enterprise-search-search-applications-creation-button"]'
);

expect(button).toHaveLength(1);
expect(button.prop('disabled')).toBeTruthy();

let popover = wrapper.find(
'div[data-test-subj="create-search-application-button-popover-content"]'
);

expect(popover).toHaveLength(0);

const hoverTarget = wrapper.find(
'div[data-test-subj="create-search-application-button-hover-target"]'
);

expect(hoverTarget).toHaveLength(1);

hoverTarget.simulate('mouseEnter');

wrapper.update();

popover = wrapper.find(
'div[data-test-subj="create-search-application-button-popover-content"]'
);

expect(popover).toHaveLength(1);
expect(popover.text()).toMatch(
'This functionality may be changed or removed completely in a future release.'
);
});
});
describe('disabled={false}', () => {
it('renders a button and shows a popover when hovered', () => {
const wrapper = mountWithIntl(<CreateSearchApplicationButton disabled={false} />);

const button = wrapper.find(
'button[data-test-subj="enterprise-search-search-applications-creation-button"]'
);

expect(button).toHaveLength(1);
expect(button.prop('disabled')).toBeFalsy();

let popover = wrapper.find(
'div[data-test-subj="create-search-application-button-popover-content"]'
);

expect(popover).toHaveLength(0);

const hoverTarget = wrapper.find(
'div[data-test-subj="create-search-application-button-hover-target"]'
);

expect(hoverTarget).toHaveLength(1);

hoverTarget.simulate('mouseEnter');

wrapper.update();

popover = wrapper.find(
'div[data-test-subj="create-search-application-button-popover-content"]'
);

expect(popover).toHaveLength(1);
expect(popover.text()).toMatch(
'This functionality may be changed or removed completely in a future release.'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';

import { useActions, useValues } from 'kea';
import useThrottle from 'react-use/lib/useThrottle';
Expand All @@ -17,10 +17,9 @@ import {
EuiFlexItem,
EuiIcon,
EuiLink,
EuiPopover,
EuiPopoverTitle,
EuiSpacer,
EuiText,
EuiToolTip,
} from '@elastic/eui';

import { i18n } from '@kbn/i18n';
Expand Down Expand Up @@ -53,38 +52,10 @@ interface CreateSearchApplicationButtonProps {
export const CreateSearchApplicationButton: React.FC<CreateSearchApplicationButtonProps> = ({
disabled,
}) => {
const [showPopover, setShowPopover] = useState<boolean>(false);

return (
<EuiPopover
isOpen={showPopover}
closePopover={() => setShowPopover(false)}
button={
<div
data-test-subj="create-search-application-button-hover-target"
onMouseEnter={() => setShowPopover(true)}
onMouseLeave={() => setShowPopover(false)}
tabIndex={0}
>
<EuiButton
fill
iconType="plusInCircle"
data-test-subj="enterprise-search-search-applications-creation-button"
data-telemetry-id="entSearchApplications-list-createSearchApplication"
isDisabled={disabled}
onClick={() => KibanaLogic.values.navigateToUrl(SEARCH_APPLICATION_CREATION_PATH)}
>
{i18n.translate(
'xpack.enterpriseSearch.searchApplications.list.createSearchApplicationButton.label',
{
defaultMessage: 'Create',
}
)}
</EuiButton>
</div>
}
>
<EuiPopoverTitle>
<EuiToolTip
position="top"
title={
<EuiFlexGroup justifyContent="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type="beaker" />
Expand All @@ -96,23 +67,35 @@ export const CreateSearchApplicationButton: React.FC<CreateSearchApplicationButt
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopoverTitle>
<div
style={{ width: '300px' }}
data-test-subj="create-search-application-button-popover-content"
}
content={
<EuiText size="s" data-test-subj="create-search-application-button-popover-content">
<FormattedMessage
id="xpack.enterpriseSearch.searchApplications.list.createSearchApplicationTechnicalPreviewPopover.body"
defaultMessage="This functionality may be changed or removed completely in a future release."
/>
</EuiText>
}
>
<EuiButton
fill
iconType="plusInCircle"
data-test-subj="enterprise-search-search-applications-creation-button"
data-telemetry-id="entSearchApplications-list-createSearchApplication"
isDisabled={disabled}
onClick={() => KibanaLogic.values.navigateToUrl(SEARCH_APPLICATION_CREATION_PATH)}
>
<EuiFlexGroup direction="column" gutterSize="m">
<EuiText size="s">
<FormattedMessage
id="xpack.enterpriseSearch.searchApplications.list.createSearchApplicationTechnicalPreviewPopover.body"
defaultMessage="This functionality may be changed or removed completely in a future release."
/>
</EuiText>
</EuiFlexGroup>
</div>
</EuiPopover>
{i18n.translate(
'xpack.enterpriseSearch.searchApplications.list.createSearchApplicationButton.label',
{
defaultMessage: 'Create',
}
)}
</EuiButton>
</EuiToolTip>
);
};

interface ListProps {
createSearchApplicationFlyoutOpen?: boolean;
}
Expand Down Expand Up @@ -223,6 +206,7 @@ export const SearchApplicationsList: React.FC<ListProps> = ({
<>
<div>
<EuiFieldSearch
data-test-subj="enterpriseSearchSearchApplicationsListFieldSearch"
value={searchQuery}
placeholder={i18n.translate(
'xpack.enterpriseSearch.searchApplications.list.searchBar.placeholder',
Expand Down

0 comments on commit 1b742e7

Please sign in to comment.