From bde5b0da90c05ef4128287a6062490046217cd2e Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 6 Dec 2023 11:06:12 +0100 Subject: [PATCH] SONAR-21029 Fix SearchSelectDropdown search input --- .../src/components/input/InputSearch.tsx | 41 +++---- .../src/components/input/SearchSelect.tsx | 24 ++--- .../input/SearchSelectControlledInput.tsx | 100 ------------------ .../components/input/SearchSelectDropdown.tsx | 3 +- .../SearchSelectControlledInput-test.tsx | 57 ---------- .../__tests__/SearchSelectDropdown-test.tsx | 4 +- .../__tests__/SecurityHotspotsApp-it.tsx | 2 +- 7 files changed, 38 insertions(+), 193 deletions(-) delete mode 100644 server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx delete mode 100644 server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx diff --git a/server/sonar-web/design-system/src/components/input/InputSearch.tsx b/server/sonar-web/design-system/src/components/input/InputSearch.tsx index 879b12165cb..c219332300e 100644 --- a/server/sonar-web/design-system/src/components/input/InputSearch.tsx +++ b/server/sonar-web/design-system/src/components/input/InputSearch.tsx @@ -39,6 +39,7 @@ interface Props { className?: string; id?: string; innerRef?: React.RefCallback; + inputId?: string; loading?: boolean; maxLength?: number; minLength?: number; @@ -55,24 +56,27 @@ interface Props { const DEFAULT_MAX_LENGTH = 100; -export function InputSearch({ - autoFocus, - id, - className, - innerRef, - onBlur, - onChange, - onFocus, - onKeyDown, - onMouseDown, - placeholder, - loading, - minLength, - maxLength = DEFAULT_MAX_LENGTH, - size = 'medium', - value: parentValue, - searchInputAriaLabel, -}: PropsWithChildren) { +export function InputSearch(props: PropsWithChildren) { + const { + autoFocus, + id, + className, + innerRef, + inputId, + onBlur, + onChange, + onFocus, + onKeyDown, + onMouseDown, + placeholder, + loading, + minLength, + maxLength = DEFAULT_MAX_LENGTH, + size = 'medium', + value: parentValue, + searchInputAriaLabel, + } = props; + const intl = useIntl(); const input = useRef(null); const [value, setValue] = useState(parentValue ?? ''); @@ -151,6 +155,7 @@ export function InputSearch({ aria-label={searchInputAriaLabel ?? placeholder} autoComplete="off" className={inputClassName} + id={inputId} maxLength={maxLength} onBlur={onBlur} onChange={handleInputChange} diff --git a/server/sonar-web/design-system/src/components/input/SearchSelect.tsx b/server/sonar-web/design-system/src/components/input/SearchSelect.tsx index 8f3ccd78d7a..951b607d53d 100644 --- a/server/sonar-web/design-system/src/components/input/SearchSelect.tsx +++ b/server/sonar-web/design-system/src/components/input/SearchSelect.tsx @@ -20,13 +20,13 @@ import classNames from 'classnames'; import { omit } from 'lodash'; import React, { RefObject } from 'react'; -import { GroupBase, InputProps, components } from 'react-select'; +import { GroupBase, InputProps } from 'react-select'; import AsyncSelect, { AsyncProps } from 'react-select/async'; import Select from 'react-select/dist/declarations/src/Select'; import { INPUT_SIZES } from '../../helpers'; import { Key } from '../../helpers/keyboard'; +import { InputSearch } from './InputSearch'; import { LabelValueSelectOption, SelectProps, selectStyle } from './InputSelect'; -import { SearchSelectControlledInput } from './SearchSelectControlledInput'; type SearchSelectProps< V, @@ -98,26 +98,24 @@ export function SearchSelectInput< const handleKeyDown = (event: React.KeyboardEvent) => { const target = event.target as HTMLInputElement; - if (event.key === Key.Escape && target.value !== '') { + if (event.key === Key.Escape.toString() && target.value !== '') { event.stopPropagation(); onChange(''); } }; return ( - = (minLength ?? 0)} minLength={minLength} onChange={onChange} + onKeyDown={handleKeyDown} + placeholder={placeholder as string} + searchInputAriaLabel={props['aria-label']} size="full" - value={inputValue} - > - - + /> ); } diff --git a/server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx b/server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx deleted file mode 100644 index f737ce04fe7..00000000000 --- a/server/sonar-web/design-system/src/components/input/SearchSelectControlledInput.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import React, { PropsWithChildren } from 'react'; -import { useIntl } from 'react-intl'; -import { INPUT_SIZES } from '../../helpers/constants'; -import { isDefined } from '../../helpers/types'; -import { InputSizeKeys } from '../../types/theme'; -import { Spinner } from '../Spinner'; -import { CloseIcon } from '../icons/CloseIcon'; -import { - InputSearchWrapper, - StyledInputWrapper, - StyledInteractiveIcon, - StyledNote, - StyledSearchIcon, -} from './InputSearch'; - -interface Props { - className?: string; - id?: string; - loading?: boolean; - minLength?: number; - onChange: (value: string) => void; - onMouseDown?: React.MouseEventHandler; - size?: InputSizeKeys; - value: string; -} - -export function SearchSelectControlledInput({ - id, - className, - onChange, - onMouseDown, - loading, - minLength, - size = 'medium', - value, - children, -}: PropsWithChildren) { - const intl = useIntl(); - const tooShort = isDefined(minLength) && value.length > 0 && value.length < minLength; - - return ( - - - {children} - - - - {value !== '' && ( - { - onChange(''); - }} - size="small" - /> - )} - - {tooShort && isDefined(minLength) && ( - - {intl.formatMessage({ id: 'select2.tooShort' }, { 0: minLength })} - - )} - - - ); -} - -SearchSelectControlledInput.displayName = 'SearchSelectControlledInput'; // so that tests don't see the obfuscated production name diff --git a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx index 355b232f9a3..44f9fb6bae8 100644 --- a/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx +++ b/server/sonar-web/design-system/src/components/input/SearchSelectDropdown.tsx @@ -169,7 +169,6 @@ export function SearchSelectDropdown< Option: IconOption, ...rest.components, }} - inputValue={inputValue} loadOptions={debouncedLoadOptions.current} menuIsOpen minLength={minLength} @@ -177,7 +176,7 @@ export function SearchSelectDropdown< onInputChange={handleInputChange} placeholder={placeholder} selectRef={ref} - value={value} + size="large" /> diff --git a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx b/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx deleted file mode 100644 index 289dd96b1e3..00000000000 --- a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectControlledInput-test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { screen } from '@testing-library/react'; -import { renderWithContext } from '../../../helpers/testUtils'; -import { FCProps } from '../../../types/misc'; -import { InputSearch } from '../InputSearch'; -import { SearchSelectControlledInput } from '../SearchSelectControlledInput'; - -it('should work properly when input is passed as a children', async () => { - const onChange = jest.fn(); - const { user } = setupWithProps({ - onChange, - value: 'foo', - children: , - }); - await user.click(screen.getByLabelText('clear')); - expect(onChange).toHaveBeenCalledWith(''); -}); - -it('should warn when input is too short', () => { - setupWithProps({ - value: 'f', - children: , - }); - expect(screen.getByRole('note')).toBeInTheDocument(); -}); - -function setupWithProps(props: Partial> = {}) { - return renderWithContext( - , - ); -} diff --git a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx b/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx index e0ae78bf93e..b2e845e831a 100644 --- a/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx +++ b/server/sonar-web/design-system/src/components/input/__tests__/SearchSelectDropdown-test.tsx @@ -44,7 +44,7 @@ it('should render select input and be able to search and select an option', asyn await user.click(screen.getByRole('combobox')); expect(screen.getByText('label1')).toBeInTheDocument(); expect(screen.getByText('different')).toBeInTheDocument(); - await user.type(screen.getByRole('combobox', { name: 'label' }), 'label'); + await user.type(screen.getByRole('searchbox', { name: 'label' }), 'label'); expect(await screen.findByText('label')).toBeInTheDocument(); expect(screen.queryByText('different')).not.toBeInTheDocument(); await user.click(screen.getByText('label')); @@ -60,7 +60,7 @@ it('should handle key navigation', async () => { renderSearchSelectDropdown(); await user.tab(); await user.keyboard('{Enter}'); - await user.type(screen.getByRole('combobox', { name: 'label' }), 'label'); + await user.type(screen.getByRole('searchbox', { name: 'label' }), 'label'); expect(await screen.findByText('label')).toBeInTheDocument(); expect(screen.queryByText('different')).not.toBeInTheDocument(); await user.keyboard('{Escape}'); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx index a7fbd4f325e..12aeddb6cca 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx @@ -90,7 +90,7 @@ const ui = { hotspotCommentBox: byRole('textbox', { name: 'hotspots.comment.field' }), hotspotStatus: byRole('heading', { name: 'status: hotspots.status_option.FIXED' }), hotspotTitle: (name: string | RegExp) => byRole('heading', { name }), - inputAssignee: byRole('combobox', { name: 'search.search_for_users' }), + inputAssignee: byRole('searchbox', { name: 'search.search_for_users' }), noHotspotForFilter: byText('hotspots.no_hotspots_for_filters.title'), openInIDEButton: byRole('button', { name: 'open_in_ide' }), panel: byTestId('security-hotspot-test'), -- 2.39.5