From 1bac3f407d5a9133dc4cdfa42e1266a48d90752c Mon Sep 17 00:00:00 2001 From: Lene Gadewoll Date: Thu, 18 Apr 2024 23:04:40 +0200 Subject: [PATCH] test(EuiComboBox): add tests for toolTipContent and toolTipProps --- src/components/combo_box/combo_box.test.tsx | 722 +++++++++++--------- 1 file changed, 385 insertions(+), 337 deletions(-) diff --git a/src/components/combo_box/combo_box.test.tsx b/src/components/combo_box/combo_box.test.tsx index c62797a2c3b6..10643adf2cc3 100644 --- a/src/components/combo_box/combo_box.test.tsx +++ b/src/components/combo_box/combo_box.test.tsx @@ -8,7 +8,11 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; -import { render, showEuiComboBoxOptions } from '../../test/rtl'; +import { + render, + showEuiComboBoxOptions, + waitForEuiToolTipVisible, +} from '../../test/rtl'; import { shouldRenderCustomStyles, testOnReactVersion, @@ -22,6 +26,8 @@ import type { EuiComboBoxOptionOption } from './types'; interface Options { 'data-test-subj'?: string; label: string; + toolTipContent?: string; + toolTipProps?: {}; } const options: Options[] = [ { @@ -216,426 +222,468 @@ describe('EuiComboBox', () => { }); }); - describe('placeholder', () => { - it('renders', () => { - const { getByTestSubject } = render( - - ); - const searchInput = getByTestSubject('comboBoxSearchInput'); + describe('toolTipContent & tooltipProps', () => { + const options = [ + { + label: 'Titan', + 'data-test-subj': 'titanOption', + toolTipContent: 'I am a tooltip!', + toolTipProps: { + 'data-test-subj': 'optionToolTip', + }, + }, + { + label: 'Enceladus', + }, + { + label: 'Mimas', + }, + ]; - expect(searchInput).toHaveAttribute('placeholder', 'Select something'); - expect(searchInput).toHaveStyle('inline-size: 100%'); - }); + it('renders a tooltip with applied props on mouseover', async () => { + const { getByTestSubject } = render(); - it('does not render the placeholder if a selection has been made', () => { - const { getByTestSubject } = render( - - ); - const searchInput = getByTestSubject('comboBoxSearchInput'); - expect(searchInput).not.toHaveAttribute('placeholder'); - }); + await showEuiComboBoxOptions(); - it('does not render the placeholder if a search value exists', () => { - const { getByTestSubject } = render( - - ); - const searchInput = getByTestSubject('comboBoxSearchInput'); - expect(searchInput).toHaveAttribute('placeholder'); + fireEvent.mouseOver(getByTestSubject('titanOption')); + await waitForEuiToolTipVisible(); - fireEvent.change(searchInput, { target: { value: 'some search' } }); - expect(searchInput).not.toHaveAttribute('placeholder'); + expect(getByTestSubject('optionToolTip')).toBeInTheDocument(); + expect(getByTestSubject('optionToolTip')).toHaveTextContent( + 'I am a tooltip!' + ); }); - }); - test('isDisabled', () => { - const { container, queryByTestSubject, queryByTitle } = render( - - ); - - expect(container.firstElementChild!.className).toContain('-isDisabled'); - expect(queryByTestSubject('comboBoxSearchInput')).toBeDisabled(); - - expect(queryByTestSubject('comboBoxClearButton')).toBeFalsy(); - expect(queryByTestSubject('comboBoxToggleListButton')).toBeFalsy(); - expect( - queryByTitle('Remove Titan from selection in this group') - ).toBeFalsy(); - }); - - test('fullWidth', () => { - // TODO: Should likely be a visual screenshot test - const { container } = render( - - ); - - expect(container.innerHTML).toContain('euiFormControlLayout--fullWidth'); - expect(container.innerHTML).toContain('euiComboBox--fullWidth'); - expect(container.innerHTML).toContain( - 'euiComboBox__inputWrap--fullWidth' - ); - }); - - test('autoFocus', () => { - const { getByTestSubject } = render( - - ); - - expect(document.activeElement).toBe( - getByTestSubject('comboBoxSearchInput') - ); - }); - - test('aria-label / aria-labelledby renders on the input, not on the wrapper', () => { - const { getByTestSubject } = render( - - ); - const input = getByTestSubject('comboBoxSearchInput'); - - expect(input).toHaveAttribute('aria-label', 'Test label'); - expect(input).toHaveAttribute('aria-labelledby', 'test-heading-id'); - }); - - test('inputRef', () => { - const inputRefCallback = jest.fn(); - - const { getByRole } = render( - - ); - expect(inputRefCallback).toHaveBeenCalledTimes(1); - - expect(getByRole('combobox')).toBe(inputRefCallback.mock.calls[0][0]); - }); - - test('onSearchChange', () => { - const onSearchChange = jest.fn(); - const { getByTestSubject, queryAllByRole } = render( - - ); - const input = getByTestSubject('comboBoxSearchInput'); - - fireEvent.change(input, { target: { value: 'no results' } }); - expect(onSearchChange).toHaveBeenCalledWith('no results', false); - expect(queryAllByRole('option')).toHaveLength(0); - - fireEvent.change(input, { target: { value: 'titan' } }); - expect(onSearchChange).toHaveBeenCalledWith('titan', true); - expect(queryAllByRole('option')).toHaveLength(2); - }); - }); - - it('does not show multiple checkmarks with duplicate labels', async () => { - const options = [ - { label: 'Titan', key: 'titan1' }, - { label: 'Titan', key: 'titan2' }, - { label: 'Tethys' }, - ]; - const { baseElement } = render( - - ); - await showEuiComboBoxOptions(); - - const dropdownOptions = baseElement.querySelectorAll( - '.euiFilterSelectItem' - ); - expect( - dropdownOptions[0]!.querySelector('[data-euiicon-type="check"]') - ).toBeFalsy(); - expect( - dropdownOptions[1]!.querySelector('[data-euiicon-type="check"]') - ).toBeTruthy(); - }); + it('renders a tooltip with applied props on focus', async () => { + const { getByTestSubject } = render(); + await showEuiComboBoxOptions(); - describe('behavior', () => { - describe('hitting "Enter"', () => { - describe('when the search input matches a value', () => { - it('selects the option', () => { - const onChange = jest.fn(); - const { getByTestSubject } = render( - - ); + const input = getByTestSubject('comboBoxSearchInput'); + fireEvent.keyDown(input, { key: keys.ARROW_DOWN }); - const input = getByTestSubject('comboBoxSearchInput'); - fireEvent.change(input, { target: { value: 'red' } }); - fireEvent.keyDown(input, { key: 'Enter' }); + await waitForEuiToolTipVisible(); - expect(onChange).toHaveBeenCalledWith([{ label: 'Red' }]); - }); + expect(getByTestSubject('optionToolTip')).toBeInTheDocument(); + expect(getByTestSubject('optionToolTip')).toHaveTextContent( + 'I am a tooltip!' + ); + }); - it('accounts for group labels', () => { - const onChange = jest.fn(); + describe('placeholder', () => { + it('renders', () => { const { getByTestSubject } = render( ); + const searchInput = getByTestSubject('comboBoxSearchInput'); - const input = getByTestSubject('comboBoxSearchInput'); - fireEvent.change(input, { target: { value: 'blue' } }); - fireEvent.keyDown(input, { key: 'Enter' }); - - expect(onChange).toHaveBeenCalledWith([{ label: 'Blue' }]); + expect(searchInput).toHaveAttribute( + 'placeholder', + 'Select something' + ); + expect(searchInput).toHaveStyle('inline-size: 100%'); }); - }); - - describe('when `onCreateOption` is passed', () => { - it('fires the callback when there is input', () => { - const onCreateOptionHandler = jest.fn(); + it('does not render the placeholder if a selection has been made', () => { const { getByTestSubject } = render( ); - const input = getByTestSubject('comboBoxSearchInput'); - - fireEvent.change(input, { target: { value: 'foo' } }); - fireEvent.keyDown(input, { key: 'Enter' }); - - expect(onCreateOptionHandler).toHaveBeenCalledTimes(1); - expect(onCreateOptionHandler).toHaveBeenCalledWith('foo', options); + const searchInput = getByTestSubject('comboBoxSearchInput'); + expect(searchInput).not.toHaveAttribute('placeholder'); }); - it('does not fire the callback when there is no input', () => { - const onCreateOptionHandler = jest.fn(); - + it('does not render the placeholder if a search value exists', () => { const { getByTestSubject } = render( - + ); - const input = getByTestSubject('comboBoxSearchInput'); + const searchInput = getByTestSubject('comboBoxSearchInput'); + expect(searchInput).toHaveAttribute('placeholder'); - fireEvent.keyDown(input, { key: 'Enter' }); - - expect(onCreateOptionHandler).not.toHaveBeenCalled(); + fireEvent.change(searchInput, { target: { value: 'some search' } }); + expect(searchInput).not.toHaveAttribute('placeholder'); }); }); - }); - describe('tabbing off the search input', () => { - it("closes the options list if the user isn't navigating the options", async () => { - const keyDownBubbled = jest.fn(); - - const { getByTestSubject } = render( -
- -
+ test('isDisabled', () => { + const { container, queryByTestSubject, queryByTitle } = render( + ); - await showEuiComboBoxOptions(); - const mockEvent = { key: keys.TAB, shiftKey: true }; - fireEvent.keyDown(getByTestSubject('comboBoxSearchInput'), mockEvent); + expect(container.firstElementChild!.className).toContain('-isDisabled'); + expect(queryByTestSubject('comboBoxSearchInput')).toBeDisabled(); - // If the TAB keydown bubbled up to the wrapper, then a browser DOM would shift the focus - expect(keyDownBubbled).toHaveBeenCalledWith( - expect.objectContaining(mockEvent) - ); + expect(queryByTestSubject('comboBoxClearButton')).toBeFalsy(); + expect(queryByTestSubject('comboBoxToggleListButton')).toBeFalsy(); + expect( + queryByTitle('Remove Titan from selection in this group') + ).toBeFalsy(); }); - it('calls onCreateOption', () => { - const onCreateOptionHandler = jest.fn(); - - const { getByTestSubject } = render( + test('fullWidth', () => { + // TODO: Should likely be a visual screenshot test + const { container } = render( ); - const input = getByTestSubject('comboBoxSearchInput'); - fireEvent.change(input, { target: { value: 'foo' } }); - fireEvent.blur(input); - - expect(onCreateOptionHandler).toHaveBeenCalledTimes(1); - expect(onCreateOptionHandler).toHaveBeenCalledWith('foo', options); - }); - - it('does nothing if the user is navigating the options', async () => { - const keyDownBubbled = jest.fn(); - - const { getByTestSubject } = render( -
- -
+ expect(container.innerHTML).toContain( + 'euiFormControlLayout--fullWidth' + ); + expect(container.innerHTML).toContain('euiComboBox--fullWidth'); + expect(container.innerHTML).toContain( + 'euiComboBox__inputWrap--fullWidth' ); - await showEuiComboBoxOptions(); - - // Navigate to an option then tab off - const input = getByTestSubject('comboBoxSearchInput'); - fireEvent.keyDown(input, { key: keys.ARROW_DOWN }); - fireEvent.keyDown(input, { key: keys.TAB }); - - // If the TAB keydown did not bubble to the wrapper, then the tab event was prevented - expect(keyDownBubbled).not.toHaveBeenCalled(); }); - }); - describe('clear button', () => { - it('renders when options are selected', () => { + test('autoFocus', () => { const { getByTestSubject } = render( - + ); - expect(getByTestSubject('comboBoxClearButton')).toBeInTheDocument(); - }); - - it('does not render when no options are selected', () => { - const { queryByTestSubject } = render( - + expect(document.activeElement).toBe( + getByTestSubject('comboBoxSearchInput') ); - - expect(queryByTestSubject('comboBoxClearButton')).toBeFalsy(); }); - it('does not render when isClearable is false', () => { - const { queryByTestSubject } = render( + test('aria-label / aria-labelledby renders on the input, not on the wrapper', () => { + const { getByTestSubject } = render( ); + const input = getByTestSubject('comboBoxSearchInput'); - expect(queryByTestSubject('comboBoxClearButton')).toBeFalsy(); + expect(input).toHaveAttribute('aria-label', 'Test label'); + expect(input).toHaveAttribute('aria-labelledby', 'test-heading-id'); }); - it('calls the onChange callback with empty array', () => { - const onChangeHandler = jest.fn(); + test('inputRef', () => { + const inputRefCallback = jest.fn(); - const { getByTestSubject } = render( - + const { getByRole } = render( + ); - fireEvent.click(getByTestSubject('comboBoxClearButton')); + expect(inputRefCallback).toHaveBeenCalledTimes(1); - expect(onChangeHandler).toHaveBeenCalledTimes(1); - expect(onChangeHandler).toHaveBeenCalledWith([]); + expect(getByRole('combobox')).toBe(inputRefCallback.mock.calls[0][0]); }); - it('focuses the input', () => { - const { getByTestSubject } = render( - {}} - /> + test('onSearchChange', () => { + const onSearchChange = jest.fn(); + const { getByTestSubject, queryAllByRole } = render( + ); - fireEvent.click(getByTestSubject('comboBoxClearButton')); + const input = getByTestSubject('comboBoxSearchInput'); - expect(document.activeElement).toBe( - getByTestSubject('comboBoxSearchInput') - ); + fireEvent.change(input, { target: { value: 'no results' } }); + expect(onSearchChange).toHaveBeenCalledWith('no results', false); + expect(queryAllByRole('option')).toHaveLength(0); + + fireEvent.change(input, { target: { value: 'titan' } }); + expect(onSearchChange).toHaveBeenCalledWith('titan', true); + expect(queryAllByRole('option')).toHaveLength(2); }); }); - describe('sortMatchesBy', () => { - const sortMatchesByOptions = [ - { label: 'Something is Disabled' }, - ...options, + it('does not show multiple checkmarks with duplicate labels', async () => { + const options = [ + { label: 'Titan', key: 'titan1' }, + { label: 'Titan', key: 'titan2' }, + { label: 'Tethys' }, ]; + const { baseElement } = render( + + ); + await showEuiComboBoxOptions(); - test('"none"', () => { - const { getByTestSubject, getAllByRole } = render( - - ); - fireEvent.change(getByTestSubject('comboBoxSearchInput'), { - target: { value: 'di' }, + const dropdownOptions = baseElement.querySelectorAll( + '.euiFilterSelectItem' + ); + expect( + dropdownOptions[0]!.querySelector('[data-euiicon-type="check"]') + ).toBeFalsy(); + expect( + dropdownOptions[1]!.querySelector('[data-euiicon-type="check"]') + ).toBeTruthy(); + }); + + describe('behavior', () => { + describe('hitting "Enter"', () => { + describe('when the search input matches a value', () => { + it('selects the option', () => { + const onChange = jest.fn(); + const { getByTestSubject } = render( + + ); + + const input = getByTestSubject('comboBoxSearchInput'); + fireEvent.change(input, { target: { value: 'red' } }); + fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onChange).toHaveBeenCalledWith([{ label: 'Red' }]); + }); + + it('accounts for group labels', () => { + const onChange = jest.fn(); + const { getByTestSubject } = render( + + ); + + const input = getByTestSubject('comboBoxSearchInput'); + fireEvent.change(input, { target: { value: 'blue' } }); + fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onChange).toHaveBeenCalledWith([{ label: 'Blue' }]); + }); }); - const foundOptions = getAllByRole('option'); - expect(foundOptions).toHaveLength(2); - expect(foundOptions[0]).toHaveTextContent('Something is Disabled'); - expect(foundOptions[1]).toHaveTextContent('Dione'); + describe('when `onCreateOption` is passed', () => { + it('fires the callback when there is input', () => { + const onCreateOptionHandler = jest.fn(); + + const { getByTestSubject } = render( + + ); + const input = getByTestSubject('comboBoxSearchInput'); + + fireEvent.change(input, { target: { value: 'foo' } }); + fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onCreateOptionHandler).toHaveBeenCalledTimes(1); + expect(onCreateOptionHandler).toHaveBeenCalledWith('foo', options); + }); + + it('does not fire the callback when there is no input', () => { + const onCreateOptionHandler = jest.fn(); + + const { getByTestSubject } = render( + + ); + const input = getByTestSubject('comboBoxSearchInput'); + + fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onCreateOptionHandler).not.toHaveBeenCalled(); + }); + }); }); - test('"startsWith"', () => { - const { getByTestSubject, getAllByRole } = render( - - ); - fireEvent.change(getByTestSubject('comboBoxSearchInput'), { - target: { value: 'di' }, + describe('tabbing off the search input', () => { + it("closes the options list if the user isn't navigating the options", async () => { + const keyDownBubbled = jest.fn(); + const { getByTestSubject } = render( +
+ +
+ ); + await showEuiComboBoxOptions(); + const mockEvent = { key: keys.TAB, shiftKey: true }; + fireEvent.keyDown(getByTestSubject('comboBoxSearchInput'), mockEvent); + // If the TAB keydown bubbled up to the wrapper, then a browser DOM would shift the focus + expect(keyDownBubbled).toHaveBeenCalledWith( + expect.objectContaining(mockEvent) + ); }); + it('calls onCreateOption', () => { + const onCreateOptionHandler = jest.fn(); + const { getByTestSubject } = render( + + ); + const input = getByTestSubject('comboBoxSearchInput'); + fireEvent.change(input, { target: { value: 'foo' } }); + fireEvent.blur(input); + expect(onCreateOptionHandler).toHaveBeenCalledTimes(1); + expect(onCreateOptionHandler).toHaveBeenCalledWith('foo', options); + }); + it('does nothing if the user is navigating the options', async () => { + const keyDownBubbled = jest.fn(); + const { getByTestSubject } = render( +
+ +
+ ); + await showEuiComboBoxOptions(); + // Navigate to an option then tab off + const input = getByTestSubject('comboBoxSearchInput'); + fireEvent.keyDown(input, { key: keys.ARROW_DOWN }); + fireEvent.keyDown(input, { key: keys.TAB }); + // If the TAB keydown did not bubble to the wrapper, then the tab event was prevented + expect(keyDownBubbled).not.toHaveBeenCalled(); + }); + }); + + describe('clear button', () => { + it('renders when options are selected', () => { + const { getByTestSubject } = render( + + ); + + expect(getByTestSubject('comboBoxClearButton')).toBeInTheDocument(); + }); + + it('does not render when no options are selected', () => { + const { queryByTestSubject } = render( + + ); + + expect(queryByTestSubject('comboBoxClearButton')).toBeFalsy(); + }); + + it('does not render when isClearable is false', () => { + const { queryByTestSubject } = render( + + ); + + expect(queryByTestSubject('comboBoxClearButton')).toBeFalsy(); + }); + + it('calls the onChange callback with empty array', () => { + const onChangeHandler = jest.fn(); + + const { getByTestSubject } = render( + + ); + fireEvent.click(getByTestSubject('comboBoxClearButton')); + + expect(onChangeHandler).toHaveBeenCalledTimes(1); + expect(onChangeHandler).toHaveBeenCalledWith([]); + }); + + it('focuses the input', () => { + const { getByTestSubject } = render( + {}} + /> + ); + fireEvent.click(getByTestSubject('comboBoxClearButton')); - const foundOptions = getAllByRole('option'); - expect(foundOptions).toHaveLength(2); - expect(foundOptions[0]).toHaveTextContent('Dione'); - expect(foundOptions[1]).toHaveTextContent('Something is Disabled'); + expect(document.activeElement).toBe( + getByTestSubject('comboBoxSearchInput') + ); + }); }); - }); - describe('isCaseSensitive', () => { - const isCaseSensitiveOptions = [{ label: 'Case sensitivity' }]; + describe('sortMatchesBy', () => { + const sortMatchesByOptions = [ + { label: 'Something is Disabled' }, + ...options, + ]; - test('false', () => { - const { getByTestSubject, queryAllByRole } = render( - - ); - fireEvent.change(getByTestSubject('comboBoxSearchInput'), { - target: { value: 'case' }, + test('"none"', () => { + const { getByTestSubject, getAllByRole } = render( + + ); + fireEvent.change(getByTestSubject('comboBoxSearchInput'), { + target: { value: 'di' }, + }); + + const foundOptions = getAllByRole('option'); + expect(foundOptions).toHaveLength(2); + expect(foundOptions[0]).toHaveTextContent('Something is Disabled'); + expect(foundOptions[1]).toHaveTextContent('Dione'); }); - expect(queryAllByRole('option')).toHaveLength(1); + test('"startsWith"', () => { + const { getByTestSubject, getAllByRole } = render( + + ); + fireEvent.change(getByTestSubject('comboBoxSearchInput'), { + target: { value: 'di' }, + }); + + const foundOptions = getAllByRole('option'); + expect(foundOptions).toHaveLength(2); + expect(foundOptions[0]).toHaveTextContent('Dione'); + expect(foundOptions[1]).toHaveTextContent('Something is Disabled'); + }); }); - test('true', () => { - const { getByTestSubject, queryAllByRole } = render( - - ); - const input = getByTestSubject('comboBoxSearchInput'); + describe('isCaseSensitive', () => { + const isCaseSensitiveOptions = [{ label: 'Case sensitivity' }]; - fireEvent.change(input, { target: { value: 'case' } }); - expect(queryAllByRole('option')).toHaveLength(0); + test('false', () => { + const { getByTestSubject, queryAllByRole } = render( + + ); + fireEvent.change(getByTestSubject('comboBoxSearchInput'), { + target: { value: 'case' }, + }); - fireEvent.change(input, { target: { value: 'Case' } }); - expect(queryAllByRole('option')).toHaveLength(1); + expect(queryAllByRole('option')).toHaveLength(1); + }); + + test('true', () => { + const { getByTestSubject, queryAllByRole } = render( + + ); + const input = getByTestSubject('comboBoxSearchInput'); + + fireEvent.change(input, { target: { value: 'case' } }); + expect(queryAllByRole('option')).toHaveLength(0); + + fireEvent.change(input, { target: { value: 'Case' } }); + expect(queryAllByRole('option')).toHaveLength(1); + }); }); }); });