diff --git a/packages/eui/changelogs/upcoming/9626.md b/packages/eui/changelogs/upcoming/9626.md new file mode 100644 index 000000000000..07fb5439a11a --- /dev/null +++ b/packages/eui/changelogs/upcoming/9626.md @@ -0,0 +1,10 @@ +- Updated `EuiToolTip` show animation to opacity-only with a 150ms grace period delay, preventing visual flickering when quickly hovering over multiple tooltip triggers + +**Bug fixes** + +- Fixed `EuiToolTip` self-hiding when the mouse moves over child elements within the trigger + +**Breaking changes** + +- Removed `delay` prop and `ToolTipDelay` type from `EuiToolTip` and `EuiIconTip` +- Removed `waitForEuiToolTipVisible` and `waitForEuiToolTipHidden` RTL test helpers; tooltip show/hide is now synchronous so direct assertions can be used instead diff --git a/packages/eui/src/components/basic_table/collapsed_item_actions.test.tsx b/packages/eui/src/components/basic_table/collapsed_item_actions.test.tsx index 60dbabcb96bf..93520be02c1d 100644 --- a/packages/eui/src/components/basic_table/collapsed_item_actions.test.tsx +++ b/packages/eui/src/components/basic_table/collapsed_item_actions.test.tsx @@ -12,8 +12,6 @@ import { render, waitForEuiPopoverOpen, waitForEuiPopoverClose, - waitForEuiToolTipVisible, - waitForEuiToolTipHidden, } from '../../test/rtl'; import { CollapsedItemActions } from './collapsed_item_actions'; @@ -80,7 +78,6 @@ describe('CollapsedItemActions', () => { expect(getByTestSubject('xyz-link')).toHaveAttribute('href', '#/xyz'); expect(getByTestSubject('xyz-link')).toHaveTextContent('name xyz'); fireEvent.mouseEnter(getByTestSubject('xyz-link')); - await waitForEuiToolTipVisible(); expect(getByText('description xyz')).toBeInTheDocument(); fireEvent.click(getByTestSubject('defaultAction')); @@ -229,7 +226,6 @@ describe('CollapsedItemActions', () => { const actionDifferent = getByTestSubject('different'); fireEvent.mouseOver(actionDifferent); - await waitForEuiToolTipVisible(); const tooltipDifferent = getByRole('tooltip'); expect(tooltipDifferent).toHaveTextContent('different'); expect(actionDifferent).toHaveAttribute('aria-describedby'); @@ -237,11 +233,9 @@ describe('CollapsedItemActions', () => { tooltipDifferent.id ); fireEvent.mouseOut(actionDifferent); - await waitForEuiToolTipHidden(); const actionSame = getByTestSubject('same'); fireEvent.mouseOver(actionSame); - await waitForEuiToolTipVisible(); const tooltipSame = getByRole('tooltip'); expect(tooltipSame).toHaveTextContent('same'); expect(actionSame).not.toHaveAttribute('aria-describedby'); diff --git a/packages/eui/src/components/basic_table/collapsed_item_actions.tsx b/packages/eui/src/components/basic_table/collapsed_item_actions.tsx index dea083d97430..742f6c251af0 100644 --- a/packages/eui/src/components/basic_table/collapsed_item_actions.tsx +++ b/packages/eui/src/components/basic_table/collapsed_item_actions.tsx @@ -115,7 +115,6 @@ export const CollapsedItemActions = ({ }} toolTipContent={toolTipContent} toolTipProps={{ - delay: 'long', // Avoid screen-readers announcing the same text twice disableScreenReaderOutput: typeof buttonContent === 'string' && @@ -148,9 +147,7 @@ export const CollapsedItemActions = ({ ); const withTooltip = !actionsDisabled && ( - - {popoverButton} - + {popoverButton} ); return ( diff --git a/packages/eui/src/components/basic_table/default_item_action.test.tsx b/packages/eui/src/components/basic_table/default_item_action.test.tsx index 9ad93e49ef34..caec6db8b888 100644 --- a/packages/eui/src/components/basic_table/default_item_action.test.tsx +++ b/packages/eui/src/components/basic_table/default_item_action.test.tsx @@ -8,11 +8,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { - render, - waitForEuiToolTipVisible, - waitForEuiToolTipHidden, -} from '../../test/rtl'; +import { render } from '../../test/rtl'; import { DefaultItemAction } from './default_item_action'; import { @@ -79,7 +75,7 @@ describe('DefaultItemAction', () => { expect(container.querySelector('.euiButtonEmpty')).toBeInTheDocument(); }); - test('props that can be functions', async () => { + test('props that can be functions', () => { const action: EmptyButtonAction = { name: ({ id }) => id === 'hello' ? Hello : world, @@ -113,17 +109,14 @@ describe('DefaultItemAction', () => { expect(secondAction).toHaveAttribute('href', '#/world'); fireEvent.mouseOver(firstAction); - await waitForEuiToolTipVisible(); expect(getByText('hello tooltip')).toBeInTheDocument(); fireEvent.mouseOut(firstAction); - await waitForEuiToolTipHidden(); fireEvent.mouseOver(secondAction); - await waitForEuiToolTipVisible(); expect(getByText('goodbye tooltip')).toBeInTheDocument(); }); - it('is described by the tooltip via aria-describedby', async () => { + it('is described by the tooltip via aria-describedby', () => { const actionWithDifferentNameAndDescription: EmptyButtonAction = { name: 'same', description: 'different', @@ -141,7 +134,6 @@ describe('DefaultItemAction', () => { const action = getByTestSubject('different'); fireEvent.mouseOver(action); - await waitForEuiToolTipVisible(); const tooltip = getByRole('tooltip'); expect(tooltip).toHaveTextContent('different'); expect(tooltip).toBeInTheDocument(); @@ -151,7 +143,7 @@ describe('DefaultItemAction', () => { // If `name` and `description` are exactly the same // we don't want screen readers announcing the same text twice - it('has visual-only tooltip when `name` equals `description`', async () => { + it('has visual-only tooltip when `name` equals `description`', () => { const actionWithEqualNameAndDescription: EmptyButtonAction = { name: 'same', description: 'same', @@ -169,7 +161,6 @@ describe('DefaultItemAction', () => { const action = getByTestSubject('same'); fireEvent.mouseOver(action); - await waitForEuiToolTipVisible(); const tooltip = getByRole('tooltip'); expect(tooltip).toBeInTheDocument(); expect(tooltip).toHaveTextContent('same'); diff --git a/packages/eui/src/components/basic_table/default_item_action.tsx b/packages/eui/src/components/basic_table/default_item_action.tsx index 4b032dcef6ee..16733202b239 100644 --- a/packages/eui/src/components/basic_table/default_item_action.tsx +++ b/packages/eui/src/components/basic_table/default_item_action.tsx @@ -60,7 +60,6 @@ export const DefaultItemAction = ({ const tooltipContent = callWithItemIfFunction(item)(action.description); const tooltipProps: Omit = { content: tooltipContent, - delay: 'long', // Avoid screen-readers announcing the same text twice disableScreenReaderOutput: typeof actionContent === 'string' && actionContent === tooltipContent, diff --git a/packages/eui/src/components/basic_table/table_types.ts b/packages/eui/src/components/basic_table/table_types.ts index 016c145486ad..7ad27e5a8ca8 100644 --- a/packages/eui/src/components/basic_table/table_types.ts +++ b/packages/eui/src/components/basic_table/table_types.ts @@ -45,9 +45,8 @@ export type EuiTableColumnNameTooltipProps = { icon?: IconType; /** Additional props for EuiIcon */ iconProps?: EuiIconTipProps['iconProps']; - /** Additional props for the EuiToolip */ - tooltipProps?: Omit & { - delay?: EuiToolTipProps['delay']; + /** Additional props for the EuiToolTip */ + tooltipProps?: Omit & { position?: EuiToolTipProps['position']; }; }; diff --git a/packages/eui/src/components/button/button_group/button_group.stories.tsx b/packages/eui/src/components/button/button_group/button_group.stories.tsx index 680dcd98aafd..3ca2d42eba52 100644 --- a/packages/eui/src/components/button/button_group/button_group.stories.tsx +++ b/packages/eui/src/components/button/button_group/button_group.stories.tsx @@ -12,7 +12,6 @@ import { disableStorybookControls } from '../../../../.storybook/utils'; import { LOKI_SELECTORS } from '../../../../.storybook/loki'; import { EuiSpacer } from '../../spacer'; -import { ToolTipDelay } from '../../tool_tip/tool_tip'; import { EuiButtonGroup, EuiButtonGroupProps, @@ -145,18 +144,14 @@ export const WithTooltips: Story = { label: 'Standard tooltip', toolTipContent: 'Hello world', autoFocus: true, // dev-only usage to showcase tooltip on load - toolTipProps: { - delay: 'none' as ToolTipDelay, // passing a (not-yet) supported value to hackishly force a lower delay for VRT - }, } as EuiButtonGroupOptionProps, { id: 'customToolTipProps', iconType: 'securitySignalDetected', label: 'Custom tooltip', - toolTipContent: 'Custom tooltip position and delay', + toolTipContent: 'Custom tooltip position', toolTipProps: { position: 'right', - delay: 'long', title: 'Hello world', }, // Consumers could also opt to hide titles if preferred diff --git a/packages/eui/src/components/button/button_group/button_group.test.tsx b/packages/eui/src/components/button/button_group/button_group.test.tsx index 5d29efb68b92..3fafd9b18b9f 100644 --- a/packages/eui/src/components/button/button_group/button_group.test.tsx +++ b/packages/eui/src/components/button/button_group/button_group.test.tsx @@ -9,12 +9,7 @@ import React from 'react'; import { css } from '@emotion/react'; import { fireEvent } from '@testing-library/react'; -import { - render, - waitForEuiToolTipHidden, - waitForEuiToolTipVisible, - focusEuiToolTipTrigger, -} from '../../../test/rtl'; +import { render, focusEuiToolTipTrigger } from '../../../test/rtl'; import { requiredProps as commonProps } from '../../../test'; import { shouldRenderCustomStyles } from '../../../test/internal'; @@ -262,19 +257,15 @@ describe('EuiButtonGroup', () => { /> ); fireEvent.mouseOver(getByTestSubject('buttonWithTooltip')); - await waitForEuiToolTipVisible(); expect(getByRole('tooltip')).toHaveTextContent('I am a tooltip'); fireEvent.mouseOut(getByTestSubject('buttonWithTooltip')); - await waitForEuiToolTipHidden(); const cleanup = focusEuiToolTipTrigger( getByTestSubject('buttonWithTooltip') ); - await waitForEuiToolTipVisible(); fireEvent.blur(getByTestSubject('buttonWithTooltip')); - await waitForEuiToolTipHidden(); cleanup(); }); @@ -299,23 +290,19 @@ describe('EuiButtonGroup', () => { // NOTE: uses `parentElement` as the hover event is triggered on the tooltip wrapper. // The button itself doesn't allow mouse events when disabled. fireEvent.mouseOver(getByTestSubject('buttonWithTooltip').parentElement!); - await waitForEuiToolTipVisible(); expect(await findByRole('tooltip')).toHaveTextContent('I am a tooltip'); fireEvent.mouseOut(getByTestSubject('buttonWithTooltip').parentElement!); - await waitForEuiToolTipHidden(); const cleanup = focusEuiToolTipTrigger( getByTestSubject('buttonWithTooltip') ); - await waitForEuiToolTipVisible(); fireEvent.blur(getByTestSubject('buttonWithTooltip')); - await waitForEuiToolTipHidden(); cleanup(); }); - it('allows customizing the tooltip via `toolTipProps`', async () => { + it('allows customizing the tooltip via `toolTipProps`', () => { const { getByTestSubject } = render( { toolTipContent: 'I am a tooltip', toolTipProps: { position: 'right', - delay: 'regular', + 'data-test-subj': 'toolTipTest', }, }, @@ -336,7 +323,6 @@ describe('EuiButtonGroup', () => { /> ); fireEvent.mouseOver(getByTestSubject('buttonWithTooltip')); - await waitForEuiToolTipVisible(); expect(getByTestSubject('toolTipTest')).toHaveAttribute( 'data-position', diff --git a/packages/eui/src/components/button/split_button/split_button.stories.tsx b/packages/eui/src/components/button/split_button/split_button.stories.tsx index f60e689a1e4e..433498591334 100644 --- a/packages/eui/src/components/button/split_button/split_button.stories.tsx +++ b/packages/eui/src/components/button/split_button/split_button.stories.tsx @@ -16,7 +16,6 @@ import { EuiSpacer } from '../../spacer'; import { EuiFlexGroup } from '../../flex'; import { EuiWrappingPopover } from '../../popover'; import { EuiContextMenu } from '../../context_menu'; -import { ToolTipDelay } from '../../tool_tip/tool_tip'; import { EuiSplitButton, EuiSplitButtonProps } from './split_button'; const decorators: Meta['decorators'] = [ @@ -90,7 +89,6 @@ export const WithTooltip: Story = { aria-label="Secondary action" tooltipProps={{ content: 'Tooltip content', - delay: 'none' as ToolTipDelay, // passing a not (yet) supported value to hackishly force a lower delay for VRT }} autoFocus={true} // VRT-only workaround to ensure an opened tooltip />, diff --git a/packages/eui/src/components/button/split_button/split_button.test.tsx b/packages/eui/src/components/button/split_button/split_button.test.tsx index 3c979266e2ec..705ce5c32cb6 100644 --- a/packages/eui/src/components/button/split_button/split_button.test.tsx +++ b/packages/eui/src/components/button/split_button/split_button.test.tsx @@ -9,11 +9,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { - render, - waitForEuiToolTipHidden, - waitForEuiToolTipVisible, -} from '../../../test/rtl'; +import { render } from '../../../test/rtl'; import { shouldRenderCustomStyles } from '../../../test/internal'; import { EuiToolTip } from '../../tool_tip'; import { EuiSplitButton, EuiSplitButtonProps } from './split_button'; @@ -294,7 +290,7 @@ describe('EuiSplitButton', () => { consoleErrorSpy.mockRestore(); }); - it('renders a tooltip', async () => { + it('renders a tooltip', () => { const { getByTestSubject } = render( { ); fireEvent.mouseOver(getByTestSubject('primary-action')); - await waitForEuiToolTipVisible(); expect(getByTestSubject('primary-action-tooltip')).toBeInTheDocument(); fireEvent.mouseLeave(getByTestSubject('primary-action')); - await waitForEuiToolTipHidden(); fireEvent.mouseOver(getByTestSubject('secondary-action')); - await waitForEuiToolTipVisible(); expect( getByTestSubject('secondary-action-tooltip') diff --git a/packages/eui/src/components/collapsible_nav_beta/collapsible_nav_item/collapsed/collapsed_nav_button.test.tsx b/packages/eui/src/components/collapsible_nav_beta/collapsible_nav_item/collapsed/collapsed_nav_button.test.tsx index c76e149f3297..a2916ec50fbc 100644 --- a/packages/eui/src/components/collapsible_nav_beta/collapsible_nav_item/collapsed/collapsed_nav_button.test.tsx +++ b/packages/eui/src/components/collapsible_nav_beta/collapsible_nav_item/collapsed/collapsed_nav_button.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; -import { render, waitForEuiToolTipVisible } from '../../../../test/rtl'; +import { render } from '../../../../test/rtl'; import { shouldRenderCustomStyles } from '../../../../test/internal'; import { requiredProps } from '../../../../test'; @@ -19,17 +19,17 @@ describe('EuiCollapsedNavButton', () => { childProps: ['linkProps'], }); - it('renders a tooltip around the icon button', async () => { + it('renders a tooltip around the icon button', () => { const { baseElement, getByTestSubject } = render( ); + fireEvent.mouseOver(getByTestSubject('euiCollapsedNavButton')); - await waitForEuiToolTipVisible(); expect(baseElement).toMatchSnapshot(); }); - it('renders isSelected', async () => { + it('renders isSelected', () => { const { container } = render( ); diff --git a/packages/eui/src/components/color_picker/color_picker_swatch.test.tsx b/packages/eui/src/components/color_picker/color_picker_swatch.test.tsx index 2a3b67000f6d..0f294ff05635 100644 --- a/packages/eui/src/components/color_picker/color_picker_swatch.test.tsx +++ b/packages/eui/src/components/color_picker/color_picker_swatch.test.tsx @@ -10,13 +10,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import { requiredProps } from '../../test'; import { shouldRenderCustomStyles } from '../../test/internal'; -import { - render, - waitForEuiToolTipHidden, - waitForEuiToolTipVisible, - focusEuiToolTipTrigger, -} from '../../test/rtl'; - +import { render, focusEuiToolTipTrigger } from '../../test/rtl'; import { EuiColorPickerSwatch } from './color_picker_swatch'; describe('EuiColorPickerSwatch', () => { @@ -29,7 +23,7 @@ describe('EuiColorPickerSwatch', () => { }); describe('showToolTip', () => { - test('it renders a color label tooltip on hover', async () => { + test('it renders a color label tooltip on hover', () => { const { getByTestSubject, getByText } = render( { fireEvent.mouseOver(swatchElement); - await waitForEuiToolTipVisible(); - expect(getByText('#000000')).toBeInTheDocument(); fireEvent.mouseLeave(swatchElement); - - await waitForEuiToolTipHidden(); }); - test('it renders a color label tooltip on focus', async () => { + test('it renders a color label tooltip on focus', () => { const { getByTestSubject, getByText } = render( { const cleanup = focusEuiToolTipTrigger(swatchElement); - await waitForEuiToolTipVisible(); - expect(getByText('#000000')).toBeInTheDocument(); fireEvent.blur(swatchElement); - await waitForEuiToolTipHidden(); cleanup(); }); - test('it does not render a color label tooltip when `showToolTip` is `false`', async () => { + test('it does not render a color label tooltip when `showToolTip` is `false`', () => { const { getByTestSubject } = render( { fireEvent.mouseOver(swatchElement); - await waitForEuiToolTipHidden(); - fireEvent.focus(swatchElement); - - await waitForEuiToolTipHidden(); }); }); }); diff --git a/packages/eui/src/components/color_picker/color_picker_swatch.tsx b/packages/eui/src/components/color_picker/color_picker_swatch.tsx index d4f43b65eb15..ec814d2ce96d 100644 --- a/packages/eui/src/components/color_picker/color_picker_swatch.tsx +++ b/packages/eui/src/components/color_picker/color_picker_swatch.tsx @@ -27,9 +27,8 @@ export type EuiColorPickerSwatchProps = CommonProps & */ showToolTip?: boolean; - /** Additional props for the EuiToolip when `showToolTip={true}` */ - toolTipProps?: Omit & { - delay?: EuiToolTipProps['delay']; + /** Additional props for the EuiToolTip when `showToolTip={true}` */ + toolTipProps?: Omit & { position?: EuiToolTipProps['position']; }; }; diff --git a/packages/eui/src/components/color_picker/hue.test.tsx b/packages/eui/src/components/color_picker/hue.test.tsx index 55716bfefad7..4846cfce37a8 100644 --- a/packages/eui/src/components/color_picker/hue.test.tsx +++ b/packages/eui/src/components/color_picker/hue.test.tsx @@ -9,12 +9,7 @@ import React from 'react'; import { requiredProps } from '../../test/required_props'; import { shouldRenderCustomStyles } from '../../test/internal'; -import { - render, - waitForEuiToolTipHidden, - waitForEuiToolTipVisible, - focusEuiToolTipTrigger, -} from '../../test/rtl'; +import { render, focusEuiToolTipTrigger } from '../../test/rtl'; import { EuiHue } from './hue'; import { fireEvent } from '@testing-library/react'; @@ -57,7 +52,7 @@ describe('EuiHue', () => { expect(container).toMatchSnapshot(); }); - test('it renders a color label tooltip on hover', async () => { + test('it renders a color label tooltip on hover', () => { const { getByText } = render( ); @@ -66,16 +61,12 @@ describe('EuiHue', () => { fireEvent.mouseOver(thumbElement); - await waitForEuiToolTipVisible(); - expect(getByText('#00FFFF')).toBeInTheDocument(); fireEvent.mouseLeave(thumbElement); - - await waitForEuiToolTipHidden(); }); - test('it renders a color label tooltip on focus', async () => { + test('it renders a color label tooltip on focus', () => { const { getByText } = render( ); @@ -84,13 +75,10 @@ describe('EuiHue', () => { const cleanup = focusEuiToolTipTrigger(thumbElement); - await waitForEuiToolTipVisible(); - expect(getByText('#00FFFF')).toBeInTheDocument(); fireEvent.blur(thumbElement); - await waitForEuiToolTipHidden(); cleanup(); }); }); diff --git a/packages/eui/src/components/color_picker/saturation.test.tsx b/packages/eui/src/components/color_picker/saturation.test.tsx index 6bae379e3f7c..0e800acc6219 100644 --- a/packages/eui/src/components/color_picker/saturation.test.tsx +++ b/packages/eui/src/components/color_picker/saturation.test.tsx @@ -9,12 +9,7 @@ import React from 'react'; import { requiredProps } from '../../test/required_props'; import { shouldRenderCustomStyles } from '../../test/internal'; -import { - render, - waitForEuiToolTipHidden, - waitForEuiToolTipVisible, - focusEuiToolTipTrigger, -} from '../../test/rtl'; +import { render, focusEuiToolTipTrigger } from '../../test/rtl'; import { EuiSaturation } from './saturation'; import { fireEvent } from '@testing-library/react'; @@ -46,7 +41,7 @@ describe('EuiSaturation', () => { expect(container.firstChild).toMatchSnapshot(); }); - test('it renders a color label tooltip on hover', async () => { + test('it renders a color label tooltip on hover', () => { const { getByText } = render( ); @@ -55,16 +50,12 @@ describe('EuiSaturation', () => { fireEvent.mouseOver(thumbElement); - await waitForEuiToolTipVisible(); - expect(getByText('#000000')).toBeInTheDocument(); fireEvent.mouseLeave(thumbElement); - - await waitForEuiToolTipHidden(); }); - test('it renders a color label tooltip on focus', async () => { + test('it renders a color label tooltip on focus', () => { const { getByText } = render( ); @@ -73,13 +64,10 @@ describe('EuiSaturation', () => { const cleanup = focusEuiToolTipTrigger(thumbElement); - await waitForEuiToolTipVisible(); - expect(getByText('#000000')).toBeInTheDocument(); fireEvent.blur(thumbElement); - await waitForEuiToolTipHidden(); cleanup(); }); }); diff --git a/packages/eui/src/components/combo_box/combo_box.test.tsx b/packages/eui/src/components/combo_box/combo_box.test.tsx index ac8031bc4f2a..48349305b355 100644 --- a/packages/eui/src/components/combo_box/combo_box.test.tsx +++ b/packages/eui/src/components/combo_box/combo_box.test.tsx @@ -8,11 +8,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { - render, - showEuiComboBoxOptions, - waitForEuiToolTipVisible, -} from '../../test/rtl'; +import { render, showEuiComboBoxOptions } from '../../test/rtl'; import { shouldRenderCustomStyles, testOnReactVersion, @@ -246,7 +242,6 @@ describe('EuiComboBox', () => { await showEuiComboBoxOptions(); fireEvent.mouseOver(getByTestSubject('titanOption')); - await waitForEuiToolTipVisible(); expect(getByTestSubject('optionToolTip')).toBeInTheDocument(); expect(getByTestSubject('optionToolTip')).toHaveTextContent( @@ -278,8 +273,6 @@ describe('EuiComboBox', () => { const input = getByTestSubject('comboBoxSearchInput'); fireEvent.keyDown(input, { key: keys.ARROW_DOWN }); - await waitForEuiToolTipVisible(); - expect(getByTestSubject('optionToolTip')).toBeInTheDocument(); expect(getByTestSubject('optionToolTip')).toHaveTextContent( 'I am a tooltip!' diff --git a/packages/eui/src/components/context_menu/context_menu_item.test.tsx b/packages/eui/src/components/context_menu/context_menu_item.test.tsx index 5ca21f6a87d1..fcf5041d2e87 100644 --- a/packages/eui/src/components/context_menu/context_menu_item.test.tsx +++ b/packages/eui/src/components/context_menu/context_menu_item.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { render, waitForEuiToolTipVisible } from '../../test/rtl'; +import { render } from '../../test/rtl'; import { shouldRenderCustomStyles } from '../../test/internal'; import { requiredProps } from '../../test/required_props'; @@ -22,9 +22,8 @@ describe('EuiContextMenuItem', () => { { childProps: ['toolTipProps', 'toolTipProps.anchorProps'], skip: { parentTest: true }, - renderCallback: async ({ getByTestSubject }) => { + renderCallback: ({ getByTestSubject }) => { fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); }, } ); @@ -154,18 +153,17 @@ describe('EuiContextMenuItem', () => { }); }); - test('tooltip behavior', async () => { + test('tooltip behavior', () => { const { getByRole, baseElement } = render( Hello ); fireEvent.mouseOver(getByRole('button')); - await waitForEuiToolTipVisible(); expect(baseElement).toMatchSnapshot(); }); diff --git a/packages/eui/src/components/copy/copy.test.tsx b/packages/eui/src/components/copy/copy.test.tsx index bb7af0a1de51..73b54f07a518 100644 --- a/packages/eui/src/components/copy/copy.test.tsx +++ b/packages/eui/src/components/copy/copy.test.tsx @@ -8,11 +8,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { - waitForEuiToolTipVisible, - waitForEuiToolTipHidden, - render, -} from '../../test/rtl'; +import { render } from '../../test/rtl'; import { requiredProps } from '../../test'; import { EuiCopy } from './copy'; @@ -44,7 +40,7 @@ describe('EuiCopy', () => { }); describe('props', () => { - it('beforeMessage', async () => { + it('beforeMessage', () => { const beforeMessage = 'copy this'; const { getByRole, getByText } = render( @@ -57,14 +53,13 @@ describe('EuiCopy', () => { ); // Simulate mouse over to show the tooltip fireEvent.mouseOver(getByRole('button')); - await waitForEuiToolTipVisible(); + // The beforeMessage should be shown in the tooltip expect(getByText(beforeMessage)).toBeInTheDocument(); fireEvent.mouseOut(getByRole('button')); - await waitForEuiToolTipHidden(); }); - it('afterMessage', async () => { + it('afterMessage', () => { const afterMessage = 'successfully copied'; const { getByRole, getByText } = render( @@ -79,14 +74,13 @@ describe('EuiCopy', () => { // Simulate a click to copy the text fireEvent.click(getByRole('button')); fireEvent.mouseOver(getByRole('button')); - await waitForEuiToolTipVisible(); + // The afterMessage should be shown after the copy action expect(getByText(afterMessage)).toBeInTheDocument(); fireEvent.mouseOut(getByRole('button')); - await waitForEuiToolTipHidden(); }); - it('tooltipProps', async () => { + it('tooltipProps', () => { const tooltipProps = { 'data-test-subj': 'customTooltip', className: 'myTooltipClass', @@ -107,13 +101,12 @@ describe('EuiCopy', () => { ); // Simulate mouse over to show the tooltip fireEvent.mouseOver(getByRole('button')); - await waitForEuiToolTipVisible(); + // The tooltip portalled, so search the global document const tooltip = getByTestSubject('customTooltip'); expect(tooltip).toBeInTheDocument(); expect(tooltip?.className).toContain('myTooltipClass'); fireEvent.mouseOut(getByRole('button')); - await waitForEuiToolTipHidden(); }); }); }); diff --git a/packages/eui/src/components/datagrid/controls/display_selector.tsx b/packages/eui/src/components/datagrid/controls/display_selector.tsx index a95dd1b8edc7..4b4414460f4d 100644 --- a/packages/eui/src/components/datagrid/controls/display_selector.tsx +++ b/packages/eui/src/components/datagrid/controls/display_selector.tsx @@ -425,7 +425,7 @@ export const useDataGridDisplaySelector = ( panelProps={{ css: logicalStyle('width', popoverWidth) }} panelClassName="euiDataGrid__displayPopoverPanel" button={ - + + { data-test-subj="trigger" showTooltip needsUpdate - toolTipProps={{ children: <>Test, delay: 'regular', position: 'top' }} // React throws a `Failed prop type` error without this + toolTipProps={{ children: <>Test, position: 'top' }} // React throws a `Failed prop type` error without this />, { childProps: ['toolTipProps'], skip: { parentTest: true }, - renderCallback: async ({ getByTestSubject }) => { + renderCallback: ({ getByTestSubject }) => { fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); }, } ); diff --git a/packages/eui/src/components/filter_group/filter_select_item.test.tsx b/packages/eui/src/components/filter_group/filter_select_item.test.tsx index b606cc20309e..1fb5a48c190f 100644 --- a/packages/eui/src/components/filter_group/filter_select_item.test.tsx +++ b/packages/eui/src/components/filter_group/filter_select_item.test.tsx @@ -7,11 +7,7 @@ */ import React from 'react'; -import { - render, - waitForEuiToolTipVisible, - waitForEuiToolTipHidden, -} from '../../test/rtl'; +import { render } from '../../test/rtl'; import { requiredProps } from '../../test'; import { shouldRenderCustomStyles } from '../../test/internal'; @@ -35,7 +31,7 @@ describe('EuiFilterSelectItem', () => { // `toggleToolTip` is called in `componentDidUpdate`; on initial mount `tooltipRef.current` // is null because `EuiToolTip` hasn't committed its ref yet, so a re-render is required // to trigger `showToolTip`/`hideToolTip`. - it('shows tooltip when `isFocused` becomes true', async () => { + it('shows tooltip when `isFocused` becomes true', () => { const { rerender, getByTestSubject } = render( Item @@ -48,11 +44,10 @@ describe('EuiFilterSelectItem', () => { ); - await waitForEuiToolTipVisible(); expect(getByTestSubject('filterItemToolTip')).toBeInTheDocument(); }); - it('hides tooltip when `isFocused` becomes false', async () => { + it('hides tooltip when `isFocused` becomes false', () => { const { rerender, queryByRole } = render( Item @@ -65,7 +60,6 @@ describe('EuiFilterSelectItem', () => { ); - await waitForEuiToolTipVisible(); expect(queryByRole('tooltip')).toBeInTheDocument(); rerender( @@ -74,7 +68,6 @@ describe('EuiFilterSelectItem', () => { ); - await waitForEuiToolTipHidden(); expect(queryByRole('tooltip')).not.toBeInTheDocument(); }); }); diff --git a/packages/eui/src/components/filter_group/filter_select_item.tsx b/packages/eui/src/components/filter_group/filter_select_item.tsx index 11f5639fa186..8f99f0840ac0 100644 --- a/packages/eui/src/components/filter_group/filter_select_item.tsx +++ b/packages/eui/src/components/filter_group/filter_select_item.tsx @@ -6,7 +6,12 @@ * Side Public License, v 1. */ -import React, { ButtonHTMLAttributes, Component, createRef } from 'react'; +import React, { + ButtonHTMLAttributes, + Component, + createRef, + isValidElement, +} from 'react'; import classNames from 'classnames'; import { withEuiTheme, WithEuiThemeProps } from '../../services'; @@ -92,12 +97,28 @@ export class EuiFilterSelectItemClass extends Component< return this.state.hasFocus; }; + componentDidMount() { + const { isFocused, toolTipContent, disabled, children } = this.props; + if (isValidElement(children) && !disabled && toolTipContent) { + this.toggleToolTip(isFocused ?? false); + } + } + componentDidUpdate( prevProps: Readonly & EuiFilterSelectItemProps> ) { if (this.props.isFocused && !prevProps.isFocused) { this.buttonRef?.scrollIntoView?.({ block: 'nearest' }); } + const { isFocused, toolTipContent, disabled, children } = this.props; + if ( + isValidElement(children) && + !disabled && + toolTipContent && + isFocused !== prevProps.isFocused + ) { + this.toggleToolTip(isFocused ?? false); + } } render() { @@ -128,7 +149,7 @@ export class EuiFilterSelectItemClass extends Component< const hasToolTip = // we're using isValidElement here as EuiToolTipAnchor uses // cloneElement to enhance the element with required attributes - React.isValidElement(children) && !disabled && toolTipContent; + isValidElement(children) && !disabled && toolTipContent; let anchorProps = undefined; @@ -143,8 +164,6 @@ export class EuiFilterSelectItemClass extends Component< style: anchorStyles, } : { style }; - - this.toggleToolTip(isFocused ?? false); } let iconNode; diff --git a/packages/eui/src/components/key_pad_menu/key_pad_menu_item.test.tsx b/packages/eui/src/components/key_pad_menu/key_pad_menu_item.test.tsx index 7fe83182e9d5..f074a4b00133 100644 --- a/packages/eui/src/components/key_pad_menu/key_pad_menu_item.test.tsx +++ b/packages/eui/src/components/key_pad_menu/key_pad_menu_item.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { render, waitForEuiToolTipVisible } from '../../test/rtl'; +import { render } from '../../test/rtl'; import { requiredProps } from '../../test'; import { shouldRenderCustomStyles } from '../../test/internal'; @@ -29,9 +29,8 @@ describe('EuiKeyPadMenuItem', () => { { skip: { parentTest: true }, childProps: ['betaBadgeTooltipProps'], - renderCallback: async ({ getByTestSubject }) => { + renderCallback: ({ getByTestSubject }) => { fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); }, } ); diff --git a/packages/eui/src/components/key_pad_menu/key_pad_menu_item.tsx b/packages/eui/src/components/key_pad_menu/key_pad_menu_item.tsx index a4d2533a6bd3..cd5e7a705147 100644 --- a/packages/eui/src/components/key_pad_menu/key_pad_menu_item.tsx +++ b/packages/eui/src/components/key_pad_menu/key_pad_menu_item.tsx @@ -83,9 +83,7 @@ type EuiKeyPadMenuItemPropsForUncheckable = { /** * Extends the wrapping EuiToolTip props when `betaBadgeLabel` is provided */ - betaBadgeTooltipProps?: Partial< - Omit - >; + betaBadgeTooltipProps?: Partial>; /** * Use `onClick` instead when the item is not `checkable` */ @@ -321,7 +319,6 @@ export const EuiKeyPadMenuItem: FunctionComponent = ({ {...betaBadgeTooltipProps} title={betaBadgeLabel} content={betaBadgeTooltipContent} - delay="long" > {button} diff --git a/packages/eui/src/components/list_group/list_group_item.test.tsx b/packages/eui/src/components/list_group/list_group_item.test.tsx index c5f6a55a8317..5d3c370a2578 100644 --- a/packages/eui/src/components/list_group/list_group_item.test.tsx +++ b/packages/eui/src/components/list_group/list_group_item.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import { shouldRenderCustomStyles } from '../../test/internal'; import { requiredProps } from '../../test/required_props'; -import { render, waitForEuiToolTipVisible } from '../../test/rtl'; +import { render } from '../../test/rtl'; import { EuiListGroupItem, SIZES, COLORS } from './list_group_item'; @@ -38,9 +38,8 @@ describe('EuiListGroupItem', () => { { childProps: ['toolTipProps', 'toolTipProps.anchorProps'], skip: { parentTest: true }, - renderCallback: async ({ getByTestSubject }) => { + renderCallback: ({ getByTestSubject }) => { fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); }, } ); @@ -255,7 +254,7 @@ describe('EuiListGroupItem', () => { }); describe('toolTipProps', () => { - test('renders custom tooltip props', async () => { + test('renders custom tooltip props', () => { const { getByTestSubject } = render( { /> ); fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); expect(getByTestSubject('tooltip')).toBeInTheDocument(); }); }); diff --git a/packages/eui/src/components/list_group/list_group_item.tsx b/packages/eui/src/components/list_group/list_group_item.tsx index e95a8eb609ee..837026c55c53 100644 --- a/packages/eui/src/components/list_group/list_group_item.tsx +++ b/packages/eui/src/components/list_group/list_group_item.tsx @@ -350,7 +350,6 @@ export const EuiListGroupItem: FunctionComponent = ({ {boldItalicButtons.map((item) => ( - + {listButtons.map((item) => ( - + {quoteCodeLinkButtons.map((item) => ( - + {uiPlugins.map(({ name, button }) => { return ( - + { }); }); - test('tooltip behavior on mouseover', async () => { + test('tooltip behavior on mouseover', () => { const { baseElement, getByTestSubject } = render( @@ -184,20 +184,19 @@ describe('EuiSelectableListItem', () => { const tooltipAnchor = baseElement.querySelector('.euiToolTipAnchor'); fireEvent.mouseOver(tooltipAnchor!); - await waitForEuiToolTipVisible(); expect(getByTestSubject('listItemToolTip')).toBeInTheDocument(); expect(baseElement).toMatchSnapshot(); }); - test('tooltip behavior when isFocused', async () => { + test('tooltip behavior when isFocused', () => { const { baseElement, getByTestSubject } = render( { ); - await waitForEuiToolTipVisible(); - expect(getByTestSubject('listItemToolTip')).toBeInTheDocument(); expect(baseElement).toMatchSnapshot(); }); diff --git a/packages/eui/src/components/table/table_header_cell.test.tsx b/packages/eui/src/components/table/table_header_cell.test.tsx index e311f3ae494b..a208793c5d93 100644 --- a/packages/eui/src/components/table/table_header_cell.test.tsx +++ b/packages/eui/src/components/table/table_header_cell.test.tsx @@ -8,7 +8,7 @@ import React, { PropsWithChildren, ReactElement } from 'react'; import { requiredProps } from '../../test/required_props'; -import { render, waitForEuiToolTipVisible } from '../../test/rtl'; +import { render } from '../../test/rtl'; import { fireEvent } from '@testing-library/react'; import { RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from '../../services'; @@ -257,7 +257,7 @@ describe('EuiTableHeaderCell', () => { }); describe('tooltip', () => { - it('renders an icon with tooltip', async () => { + it('renders an icon with tooltip', () => { const { getByTestSubject } = renderInTableHeader( { ); fireEvent.mouseOver(getByTestSubject('icon')); - await waitForEuiToolTipVisible(); expect(getByTestSubject('tooltip')).toHaveTextContent( 'This is the content of the tooltip' ); }); - it('renders a tooltip on the cell if sortable', async () => { + it('renders a tooltip on the cell if sortable', () => { const { getByTestSubject } = renderInTableHeader( { ); fireEvent.mouseOver(getByTestSubject('tableHeaderSortButton')); - await waitForEuiToolTipVisible(); expect(getByTestSubject('tooltip')).toHaveTextContent( 'This is the content of the tooltip' diff --git a/packages/eui/src/components/tool_tip/icon_tip.stories.tsx b/packages/eui/src/components/tool_tip/icon_tip.stories.tsx index c03db00ff451..b591e4c43986 100644 --- a/packages/eui/src/components/tool_tip/icon_tip.stories.tsx +++ b/packages/eui/src/components/tool_tip/icon_tip.stories.tsx @@ -15,7 +15,6 @@ import { } from '../../../.storybook/utils'; import { LOKI_SELECTORS } from '../../../.storybook/loki'; import { EuiFlexGroup } from '../flex'; -import { ToolTipDelay } from './tool_tip'; import { EuiIconTip, EuiIconTipProps } from './icon_tip'; const meta: Meta = { @@ -43,7 +42,6 @@ const meta: Meta = { args: { type: 'question', position: 'top', - delay: 'regular', display: 'inlineBlock', // set up for easier testing/QA anchorClassName: '', @@ -79,6 +77,5 @@ export const Playground: Story = { // @ts-ignore - temp. solution for storybook VRT testing autofocus: 'true', }, - delay: 'none' as ToolTipDelay, // passing a (not-yet) supported value to hackishly force a lower delay for VRT }, }; diff --git a/packages/eui/src/components/tool_tip/icon_tip.test.tsx b/packages/eui/src/components/tool_tip/icon_tip.test.tsx index 86fe8e3d2745..45ce9c5613b3 100644 --- a/packages/eui/src/components/tool_tip/icon_tip.test.tsx +++ b/packages/eui/src/components/tool_tip/icon_tip.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import { requiredProps } from '../../test'; -import { render, waitForEuiToolTipVisible } from '../../test/rtl'; +import { render } from '../../test/rtl'; import { shouldRenderCustomStyles } from '../../test/internal'; import { EuiIconTip } from './icon_tip'; @@ -19,9 +19,8 @@ describe('EuiIconTip', () => { , { childProps: ['iconProps'], - renderCallback: async ({ getByTestSubject }) => { + renderCallback: ({ getByTestSubject }) => { fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); }, } ); @@ -89,13 +88,12 @@ describe('EuiIconTip', () => { }); }); - it('shows tooltip content on hover', async () => { + it('shows tooltip content on hover', () => { const { container, getByRole } = render( ); fireEvent.mouseOver(container.querySelector('[data-euiicon-type]')!); - await waitForEuiToolTipVisible(); expect(getByRole('tooltip')).toHaveTextContent('Tooltip content'); }); diff --git a/packages/eui/src/components/tool_tip/icon_tip.tsx b/packages/eui/src/components/tool_tip/icon_tip.tsx index 19554f7c6db7..8c0114b55cde 100644 --- a/packages/eui/src/components/tool_tip/icon_tip.tsx +++ b/packages/eui/src/components/tool_tip/icon_tip.tsx @@ -5,6 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ +/* eslint-disable @elastic/eui/prefer-eui-icon-tip */ import React, { FunctionComponent } from 'react'; @@ -13,10 +14,7 @@ import { useEuiI18n } from '../i18n'; import { EuiIcon, IconSize, IconType } from '../icon'; import { EuiToolTip, EuiToolTipProps } from './tool_tip'; -export type EuiIconTipProps = Omit< - EuiToolTipProps, - 'children' | 'delay' | 'position' -> & { +export type EuiIconTipProps = Omit & { /** * Children are not allowed as they are built using the icon props */ @@ -44,9 +42,7 @@ export type EuiIconTipProps = Omit< // iconProps; however, due to TS's bivariant function arguments `type` could be // passed without any error/feedback so we explicitly set it to `never` type iconProps?: Omit, 'type'> & { type?: never }; - // This are copied from EuiToolTipProps, but made optional. Defaults - // are applied below. - delay?: EuiToolTipProps['delay']; + // Copied from EuiToolTipProps, but made optional. Default applied below. position?: EuiToolTipProps['position']; }; @@ -57,13 +53,12 @@ export const EuiIconTip: FunctionComponent = ({ size, iconProps, position = 'top', - delay = 'regular', ...rest }) => { const defaultAriaLabel = useEuiI18n('euiIconTip.defaultAriaLabel', 'Info'); return ( - + = { ], args: { position: 'top', - delay: 'regular', display: 'inlineBlock', // set up for easier testing/QA anchorClassName: '', diff --git a/packages/eui/src/components/tool_tip/tool_tip.styles.ts b/packages/eui/src/components/tool_tip/tool_tip.styles.ts index 915d46597d75..dcf54828857b 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.styles.ts +++ b/packages/eui/src/components/tool_tip/tool_tip.styles.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { css } from '@emotion/react'; -import { euiShadow } from '@elastic/eui-theme-common'; +import { css, keyframes } from '@emotion/react'; +import { euiCanAnimate, euiShadow } from '@elastic/eui-theme-common'; import { logicalCSS, euiFontSize } from '../../global_styling'; import { UseEuiTheme } from '../../services'; @@ -20,6 +20,11 @@ export const euiToolTipBackgroundColor = (euiTheme: UseEuiTheme['euiTheme']) => export const euiToolTipBorderColor = (euiTheme: UseEuiTheme['euiTheme']) => euiTheme.components.tooltipBorder; +const euiToolTipAnimation = keyframes` + from { opacity: 0; } + to { opacity: 1; } +`; + export const euiToolTipStyles = (euiThemeContext: UseEuiTheme) => { const { euiTheme, highContrastMode } = euiThemeContext; @@ -48,6 +53,11 @@ export const euiToolTipStyles = (euiThemeContext: UseEuiTheme) => { [class*='euiHorizontalRule'] { background-color: ${euiToolTipBorderColor(euiTheme)}; } + + ${euiCanAnimate} { + animation: ${euiToolTipAnimation} ${euiTheme.animation.extraFast} + ease-out ${euiTheme.animation.fast} both; + } `, // Positions - kept for component compatibility. Animation is in the base style. top: css``, diff --git a/packages/eui/src/components/tool_tip/tool_tip.test.tsx b/packages/eui/src/components/tool_tip/tool_tip.test.tsx index f8543d98aaff..16f2310752d7 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.test.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.test.tsx @@ -8,11 +8,11 @@ import React, { createRef, StrictMode, useRef } from 'react'; import { act, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { render, - waitForEuiToolTipVisible, - waitForEuiToolTipHidden, focusEuiToolTipTrigger, + simulateFocusVisible, } from '../../test/rtl'; import { requiredProps } from '../../test'; import { shouldRenderCustomStyles } from '../../test/internal'; @@ -27,9 +27,8 @@ describe('EuiToolTip', () => { , { childProps: ['anchorProps'], - renderCallback: async ({ getByTestSubject }) => { + renderCallback: ({ getByTestSubject }) => { fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); }, } ); @@ -45,11 +44,7 @@ describe('EuiToolTip', () => { }); describe('visibility', () => { - afterEach(() => { - jest.useRealTimers(); - }); - - it('shows on mouseover and hides on mouseout', async () => { + it('shows on mouseover and hides on mouseout', () => { const { getByTestSubject, queryByRole } = render( @@ -59,15 +54,13 @@ describe('EuiToolTip', () => { expect(queryByRole('tooltip')).not.toBeInTheDocument(); fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); expect(queryByRole('tooltip')).toBeInTheDocument(); fireEvent.mouseOut(getByTestSubject('trigger')); - await waitForEuiToolTipHidden(); expect(queryByRole('tooltip')).not.toBeInTheDocument(); }); - it('shows on initial autoFocus in StrictMode', async () => { + it('shows on initial `autoFocus` in StrictMode', () => { const originalMatches = Element.prototype.matches; const spy = jest .spyOn(Element.prototype, 'matches') @@ -88,51 +81,41 @@ describe('EuiToolTip', () => { ); - await waitForEuiToolTipVisible(); expect(queryByRole('tooltip')).toBeInTheDocument(); fireEvent.blur(getByTestSubject('trigger')); - await waitForEuiToolTipHidden(); expect(queryByRole('tooltip')).not.toBeInTheDocument(); } finally { spy.mockRestore(); } }); - it('shows on keyboard focus and hides on blur', async () => { + it('shows on keyboard focus and hides on blur', () => { const { getByTestSubject, queryByRole } = render( - - - + + + + + ); expect(queryByRole('tooltip')).not.toBeInTheDocument(); const trigger = getByTestSubject('trigger'); - const cleanup = focusEuiToolTipTrigger(trigger); - await waitForEuiToolTipVisible(); + const cleanup = simulateFocusVisible(trigger); + + act(() => { + userEvent.tab(); + }); expect(queryByRole('tooltip')).toBeInTheDocument(); fireEvent.blur(trigger); - await waitForEuiToolTipHidden(); expect(queryByRole('tooltip')).not.toBeInTheDocument(); - cleanup(); - }); - - it('does not show on mouse-click focus', () => { - const { getByTestSubject, queryByRole } = render( - - - - ); - // Intentionally using plain `fireEvent.focus` (no `:focus-visible`) to simulate mouse-click focus - // eslint-disable-next-line @elastic/eui/prefer-tooltip-trigger-focus-test-utility - fireEvent.focus(getByTestSubject('trigger')); - expect(queryByRole('tooltip')).not.toBeInTheDocument(); + cleanup(); }); - it('persists tooltip on mouseout when trigger was keyboard-focused', async () => { + it('persists on mouseout when trigger was keyboard-focused', () => { const { getByTestSubject, queryByRole } = render( @@ -140,16 +123,17 @@ describe('EuiToolTip', () => { ); const trigger = getByTestSubject('trigger'); - const cleanup = focusEuiToolTipTrigger(trigger); - await waitForEuiToolTipVisible(); + focusEuiToolTipTrigger(trigger); fireEvent.mouseOut(getByTestSubject('trigger')); // Tooltip stays visible because `hasFocus=true` (keyboard focus) expect(queryByRole('tooltip')).toBeInTheDocument(); - cleanup(); + + fireEvent.blur(getByTestSubject('trigger')); + expect(queryByRole('tooltip')).not.toBeInTheDocument(); }); - it('hides tooltip on mouseout when trigger was mouse-click focused', async () => { + it('hides on mouseout when trigger was mouse-click focused', () => { const { getByTestSubject, queryByRole } = render( @@ -158,19 +142,16 @@ describe('EuiToolTip', () => { // Show on hover first, then click-focus (no `:focus-visible`) fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); // Intentionally using plain `fireEvent.focus` (no `:focus-visible`) to simulate mouse-click focus // eslint-disable-next-line @elastic/eui/prefer-tooltip-trigger-focus-test-utility fireEvent.focus(getByTestSubject('trigger')); fireEvent.mouseOut(getByTestSubject('trigger')); // Tooltip hides because `hasFocus` was not set (click focus, not keyboard) - await waitForEuiToolTipHidden(); expect(queryByRole('tooltip')).not.toBeInTheDocument(); }); it('does not render when neither content nor title are provided', () => { - jest.useFakeTimers(); const { queryByRole, getByTestSubject } = render( @@ -178,13 +159,11 @@ describe('EuiToolTip', () => { ); fireEvent.mouseOver(getByTestSubject('trigger')); - // Flush tooltip delay and state queue - act(() => jest.runAllTimers()); expect(queryByRole('tooltip')).not.toBeInTheDocument(); }); - it('renders with title only and no content', async () => { + it('renders with title only and no content', () => { const { getByTestSubject, getByRole } = render( @@ -192,14 +171,13 @@ describe('EuiToolTip', () => { ); fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); expect(getByRole('tooltip')).toHaveTextContent('Tooltip title'); }); }); describe('props', () => { - it('applies anchorClassName and anchorProps to the anchor wrapper', () => { + it('applies `anchorClassName` and `anchorProps` to the anchor wrapper', () => { const { container } = render( { expect(anchor).toHaveAttribute('data-test-subj', 'anchor'); }); - it('display="block" applies a different CSS class than display="inlineBlock"', () => { + it('`display="block"` applies a different CSS class than `display="inlineBlock"`', () => { const { container: blockContainer } = render( @@ -233,7 +211,7 @@ describe('EuiToolTip', () => { expect(blockAnchor.className).not.toEqual(inlineBlockAnchor.className); }); - it('calls the onMouseOut prop callback on mouseout', async () => { + it('calls the `onMouseOut` prop callback on mouseout', () => { const onMouseOut = jest.fn(); const { getByTestSubject } = render( @@ -242,7 +220,6 @@ describe('EuiToolTip', () => { ); fireEvent.mouseOver(getByTestSubject('trigger')); - await waitForEuiToolTipVisible(); fireEvent.mouseOut(getByTestSubject('trigger')); expect(onMouseOut).toHaveBeenCalledTimes(1); @@ -250,14 +227,13 @@ describe('EuiToolTip', () => { }); describe('aria-describedby', () => { - it('by default, sets an `aria-describedby` on the anchor when the tooltip is visible', async () => { + it('by default, sets an `aria-describedby` on the anchor when the tooltip is visible', () => { const { getByTestSubject } = render(