diff options
author | Philippe Perrin <philippe.perrin@sonarsource.com> | 2022-03-29 15:41:18 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-03-30 20:03:10 +0000 |
commit | 1949f6aad24ea81cc3dccaf41953b0809ec69e88 (patch) | |
tree | d8995b41eb485e7f5cd789216d6c8f5cc1241314 /server/sonar-web/src | |
parent | 452e52ba38fb5939ceec03c6591163cc20cb95af (diff) | |
download | sonarqube-1949f6aad24ea81cc3dccaf41953b0809ec69e88.tar.gz sonarqube-1949f6aad24ea81cc3dccaf41953b0809ec69e88.zip |
SONAR-16091 Update SelectLegacy
Diffstat (limited to 'server/sonar-web/src')
14 files changed, 237 insertions, 698 deletions
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/FilterBar.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/FilterBar.tsx index ccbb2979f25..3a7e2733616 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/FilterBar.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/FilterBar.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; import HelpTooltip from '../../../components/controls/HelpTooltip'; import RadioToggle from '../../../components/controls/RadioToggle'; -import SelectLegacy from '../../../components/controls/SelectLegacy'; +import Select from '../../../components/controls/Select'; import Measure from '../../../components/measure/Measure'; import CoverageRating from '../../../components/ui/CoverageRating'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; @@ -43,7 +43,7 @@ export interface FilterBarProps { onShowAllHotspots: () => void; } -const statusOptions: Array<{ label: string; value: string }> = [ +const statusOptions: Array<{ label: string; value: HotspotStatusFilter }> = [ { value: HotspotStatusFilter.TO_REVIEW, label: translate('hotspot.filters.status.to_review') }, { value: HotspotStatusFilter.ACKNOWLEDGED, @@ -111,28 +111,28 @@ export function FilterBar(props: FilterBarProps) { /> )} - <span className="spacer-right">{translate('status')}</span> - <SelectLegacy + <span className="spacer-right"> {translate('status')} </span> + <Select className="input-medium big-spacer-right" - clearable={false} + aria-label={translate('hotspot.filters.status')} onChange={(option: { value: HotspotStatusFilter }) => props.onChangeFilters({ status: option.value }) } options={statusOptions} - searchable={false} - value={filters.status} + isSearchable={false} + value={statusOptions.find(status => status.value === filters.status)} /> {onBranch && ( - <SelectLegacy + <Select className="input-medium big-spacer-right" - clearable={false} + aria-label={translate('hotspot.filters.period')} onChange={(option: { value: boolean }) => props.onChangeFilters({ sinceLeakPeriod: option.value }) } options={periodOptions} - searchable={false} - value={filters.sinceLeakPeriod} + isSearchable={false} + value={periodOptions.find(period => period.value === filters.sinceLeakPeriod)} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx index 91a81f57ebc..1cb08607edc 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx @@ -20,7 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import RadioToggle from '../../../../components/controls/RadioToggle'; -import SelectLegacy from '../../../../components/controls/SelectLegacy'; +import Select from '../../../../components/controls/Select'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; import { ComponentQualifier } from '../../../../types/component'; @@ -60,11 +60,11 @@ it('should trigger onChange for status', () => { const wrapper = shallowRender({ onChangeFilters }); const { onChange } = wrapper - .find(SelectLegacy) + .find(Select) .at(0) .props(); - onChange!({ value: HotspotStatusFilter.SAFE }); + onChange({ value: HotspotStatusFilter.SAFE }); expect(onChangeFilters).toBeCalledWith({ status: HotspotStatusFilter.SAFE }); }); @@ -74,7 +74,7 @@ it('should trigger onChange for self-assigned toggle', () => { const { onCheck } = wrapper.find(RadioToggle).props(); - onCheck!(AssigneeFilterOption.ALL); + onCheck(AssigneeFilterOption.ALL); expect(onChangeFilters).toBeCalledWith({ assignedToMe: false }); }); @@ -83,11 +83,11 @@ it('should trigger onChange for leak period', () => { const wrapper = shallowRender({ onChangeFilters }); const { onChange } = wrapper - .find(SelectLegacy) + .find(Select) .at(1) .props(); - onChange!({ value: true }); + onChange({ value: true }); expect(onChangeFilters).toBeCalledWith({ sinceLeakPeriod: true }); }); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap index 89dd2770842..9c037b787b2 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap @@ -47,11 +47,14 @@ exports[`should render correctly: anonymous 1`] = ` <span className="spacer-right" > + status + </span> - <SelectLegacy + <Select + aria-label="hotspot.filters.status" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -73,12 +76,17 @@ exports[`should render correctly: anonymous 1`] = ` }, ] } - searchable={false} - value="TO_REVIEW" + value={ + Object { + "label": "hotspot.filters.status.to_review", + "value": "TO_REVIEW", + } + } /> - <SelectLegacy + <Select + aria-label="hotspot.filters.period" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -92,8 +100,12 @@ exports[`should render correctly: anonymous 1`] = ` }, ] } - searchable={false} - value={false} + value={ + Object { + "label": "hotspot.filters.period.overall", + "value": false, + } + } /> </div> <div @@ -167,11 +179,14 @@ exports[`should render correctly: logged-in 1`] = ` <span className="spacer-right" > + status + </span> - <SelectLegacy + <Select + aria-label="hotspot.filters.status" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -193,12 +208,17 @@ exports[`should render correctly: logged-in 1`] = ` }, ] } - searchable={false} - value="TO_REVIEW" + value={ + Object { + "label": "hotspot.filters.status.to_review", + "value": "TO_REVIEW", + } + } /> - <SelectLegacy + <Select + aria-label="hotspot.filters.period" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -212,8 +232,12 @@ exports[`should render correctly: logged-in 1`] = ` }, ] } - searchable={false} - value={false} + value={ + Object { + "label": "hotspot.filters.period.overall", + "value": false, + } + } /> </div> <div @@ -287,11 +311,14 @@ exports[`should render correctly: non-project 1`] = ` <span className="spacer-right" > + status + </span> - <SelectLegacy + <Select + aria-label="hotspot.filters.status" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -313,12 +340,17 @@ exports[`should render correctly: non-project 1`] = ` }, ] } - searchable={false} - value="TO_REVIEW" + value={ + Object { + "label": "hotspot.filters.status.to_review", + "value": "TO_REVIEW", + } + } /> - <SelectLegacy + <Select + aria-label="hotspot.filters.period" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -332,8 +364,12 @@ exports[`should render correctly: non-project 1`] = ` }, ] } - searchable={false} - value={false} + value={ + Object { + "label": "hotspot.filters.period.overall", + "value": false, + } + } /> </div> </div> @@ -366,11 +402,14 @@ exports[`should render correctly: on Pull request 1`] = ` <span className="spacer-right" > + status + </span> - <SelectLegacy + <Select + aria-label="hotspot.filters.status" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -392,8 +431,12 @@ exports[`should render correctly: on Pull request 1`] = ` }, ] } - searchable={false} - value="TO_REVIEW" + value={ + Object { + "label": "hotspot.filters.status.to_review", + "value": "TO_REVIEW", + } + } /> </div> <div @@ -448,11 +491,14 @@ exports[`should render correctly: with hotspots reviewed measure 1`] = ` <span className="spacer-right" > + status + </span> - <SelectLegacy + <Select + aria-label="hotspot.filters.status" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -474,12 +520,17 @@ exports[`should render correctly: with hotspots reviewed measure 1`] = ` }, ] } - searchable={false} - value="TO_REVIEW" + value={ + Object { + "label": "hotspot.filters.status.to_review", + "value": "TO_REVIEW", + } + } /> - <SelectLegacy + <Select + aria-label="hotspot.filters.period" className="input-medium big-spacer-right" - clearable={false} + isSearchable={false} onChange={[Function]} options={ Array [ @@ -493,8 +544,12 @@ exports[`should render correctly: with hotspots reviewed measure 1`] = ` }, ] } - searchable={false} - value={false} + value={ + Object { + "label": "hotspot.filters.period.overall", + "value": false, + } + } /> </div> <div diff --git a/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx b/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx index 7ffb1d26415..83adcaee686 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import SelectLegacy from '../../../components/controls/SelectLegacy'; +import Select from '../../../components/controls/Select'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import { translate } from '../../../helpers/l10n'; import { getCategoryName } from '../utils'; @@ -51,14 +51,17 @@ export function Languages(props: LanguagesProps) { return ( <> - <h2 className="settings-sub-category-name">{translate('property.category.languages')}</h2> + <h2 id="languages-category-title" className="settings-sub-category-name"> + {translate('property.category.languages')} + </h2> <div data-test="language-select"> - <SelectLegacy - className="input-large" + <Select + aria-labelledby="languages-category-title" + className="input-large select-settings-language" onChange={handleOnChange} options={availableLanguages} placeholder={translate('settings.languages.select_a_language_placeholder')} - value={selectedLanguage} + value={availableLanguages.find(language => language.value === selectedLanguage)} /> </div> {selectedLanguage && ( diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx index 9e9b03df383..6e44066cdcb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx @@ -19,7 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import SelectLegacy from '../../../../components/controls/SelectLegacy'; +import Select from '../../../../components/controls/Select'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; import CategoryDefinitionsList from '../CategoryDefinitionsList'; @@ -41,9 +41,9 @@ it('should correctly handle a change of the selected language', () => { const wrapper = shallowRender({ router }); expect(wrapper.find(CategoryDefinitionsList).props().category).toBe('java'); - const { onChange } = wrapper.find(SelectLegacy).props(); + const { onChange } = wrapper.find(Select).props(); - onChange!({ label: '', originalValue: 'CoBoL', value: 'cobol' }); + onChange({ label: '', originalValue: 'CoBoL', value: 'cobol' }); expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } })); }); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap index 0659334976a..b44e852341a 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap @@ -4,14 +4,16 @@ exports[`should render correctly 1`] = ` <Fragment> <h2 className="settings-sub-category-name" + id="languages-category-title" > property.category.languages </h2> <div data-test="language-select" > - <SelectLegacy - className="input-large" + <Select + aria-labelledby="languages-category-title" + className="input-large select-settings-language" onChange={[Function]} options={ Array [ @@ -33,7 +35,13 @@ exports[`should render correctly 1`] = ` ] } placeholder="settings.languages.select_a_language_placeholder" - value="java" + value={ + Object { + "label": "property.category.Java", + "originalValue": "Java", + "value": "java", + } + } /> </div> <div @@ -51,14 +59,16 @@ exports[`should render correctly with an unknow language 1`] = ` <Fragment> <h2 className="settings-sub-category-name" + id="languages-category-title" > property.category.languages </h2> <div data-test="language-select" > - <SelectLegacy - className="input-large" + <Select + aria-labelledby="languages-category-title" + className="input-large select-settings-language" onChange={[Function]} options={ Array [ diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx index 19285a1f935..6b41d66209b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import SelectLegacy from '../../../../components/controls/SelectLegacy'; +import Select from '../../../../components/controls/Select'; import { ExtendedSettingDefinition } from '../../../../types/settings'; import { DefaultSpecializedInputProps } from '../../utils'; @@ -30,19 +30,20 @@ export default class InputForSingleSelectList extends React.PureComponent<Props> }; render() { - const options = this.props.options.map(option => ({ + const { options: opts, name, value } = this.props; + + const options = opts.map(option => ({ label: option, value: option })); return ( - <SelectLegacy + <Select className="settings-large-input" - clearable={false} - name={this.props.name} + name={name} onChange={this.handleInputChange} options={options} - value={this.props.value} + value={options.find(option => option.value === value)} /> ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx index c6a9972e4cf..63172dd9079 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx @@ -19,17 +19,17 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import SelectLegacy from '../../../../../components/controls/SelectLegacy'; +import Select from '../../../../../components/controls/Select'; import { mockSetting } from '../../../../../helpers/mocks/settings'; import { DefaultSpecializedInputProps } from '../../../utils'; import InputForSingleSelectList from '../InputForSingleSelectList'; it('should render Select', () => { const onChange = jest.fn(); - const select = shallowRender({ onChange }).find(SelectLegacy); + const select = shallowRender({ onChange }).find(Select); expect(select.length).toBe(1); expect(select.prop('name')).toBe('foo'); - expect(select.prop('value')).toBe('bar'); + expect(select.prop('value')).toEqual({ label: 'bar', value: 'bar' }); expect(select.prop('options')).toEqual([ { value: 'foo', label: 'foo' }, { value: 'bar', label: 'bar' }, @@ -40,7 +40,7 @@ it('should render Select', () => { it('should call onChange', () => { const onChange = jest.fn(); - const select = shallowRender({ onChange }).find(SelectLegacy); + const select = shallowRender({ onChange }).find(Select); expect(select.length).toBe(1); expect(select.prop('onChange')).toBeDefined(); diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx index 29162ee65e5..b74af2527a5 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx @@ -20,8 +20,9 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; +import { components, OptionProps, SingleValueProps } from 'react-select'; import { Button, SubmitButton } from '../../../../components/controls/buttons'; -import SelectLegacy from '../../../../components/controls/SelectLegacy'; +import Select from '../../../../components/controls/Select'; import AlertSuccessIcon from '../../../../components/icons/AlertSuccessIcon'; import { Alert } from '../../../../components/ui/Alert'; import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; @@ -56,7 +57,15 @@ export interface PRDecorationBindingRendererProps { isSysAdmin: boolean; } -function optionRenderer(instance: AlmSettingsInstance) { +function optionRenderer(props: OptionProps<AlmSettingsInstance, false>) { + return <components.Option {...props}>{customOptions(props.data)}</components.Option>; +} + +function singleValueRenderer(props: SingleValueProps<AlmSettingsInstance>) { + return <components.SingleValue {...props}>{customOptions(props.data)}</components.SingleValue>; +} + +function customOptions(instance: AlmSettingsInstance) { return instance.url ? ( <> <span>{instance.key} — </span> @@ -141,22 +150,18 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe </div> </div> <div className="settings-definition-right"> - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={{ - maxWidth: '210%' /* Allow double the width of the select */, - width: 'auto' - }} - onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)} - optionRenderer={optionRenderer} + <Select + inputId="name" + className="abs-width-400 big-spacer-top it__configuration-name-select" + isClearable={false} + isSearchable={false} options={instances} - searchable={false} - value={formData.key} - valueKey="key" - valueRenderer={optionRenderer} + onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)} + components={{ + Option: optionRenderer, + SingleValue: singleValueRenderer + }} + value={instances.filter(instance => instance.key === formData.key)} /> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx index a4bf1454e6d..d61e57e0ea6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx @@ -19,8 +19,6 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import SelectLegacy from '../../../../../components/controls/SelectLegacy'; -import { waitAndUpdate } from '../../../../../helpers/testUtils'; import { AlmKeys, AlmSettingsInstance, @@ -124,18 +122,6 @@ it.each([ } ); -it('should render select options correctly', async () => { - const wrapper = shallowRender({ instances }); - - await waitAndUpdate(wrapper); - - const { optionRenderer } = wrapper.find(SelectLegacy).props(); - - expect(optionRenderer!(instances[0])).toMatchSnapshot(); - - expect(optionRenderer!(instances[1])).toMatchSnapshot(); -}); - function shallowRender(props: Partial<PRDecorationBindingRendererProps> = {}) { return shallow( <PRDecorationBindingRenderer diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap index f1bf70fdb61..b72c9f4121e 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap @@ -129,19 +129,18 @@ exports[`should render correctly: when there are configuration errors (admin use <div className="settings-definition-right" > - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={ + <Select + className="abs-width-400 big-spacer-top it__configuration-name-select" + components={ Object { - "maxWidth": "210%", - "width": "auto", + "Option": [Function], + "SingleValue": [Function], } } + inputId="name" + isClearable={false} + isSearchable={false} onChange={[Function]} - optionRenderer={[Function]} options={ Array [ Object { @@ -165,10 +164,15 @@ exports[`should render correctly: when there are configuration errors (admin use }, ] } - searchable={false} - value="i1" - valueKey="key" - valueRenderer={[Function]} + value={ + Array [ + Object { + "alm": "github", + "key": "i1", + "url": "http://github.enterprise.com", + }, + ] + } /> </div> </div> @@ -330,19 +334,18 @@ exports[`should render correctly: when there are configuration errors (admin use <div className="settings-definition-right" > - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={ + <Select + className="abs-width-400 big-spacer-top it__configuration-name-select" + components={ Object { - "maxWidth": "210%", - "width": "auto", + "Option": [Function], + "SingleValue": [Function], } } + inputId="name" + isClearable={false} + isSearchable={false} onChange={[Function]} - optionRenderer={[Function]} options={ Array [ Object { @@ -366,10 +369,7 @@ exports[`should render correctly: when there are configuration errors (admin use }, ] } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} + value={Array []} /> </div> </div> @@ -472,19 +472,18 @@ exports[`should render correctly: when there are configuration errors (non-admin <div className="settings-definition-right" > - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={ + <Select + className="abs-width-400 big-spacer-top it__configuration-name-select" + components={ Object { - "maxWidth": "210%", - "width": "auto", + "Option": [Function], + "SingleValue": [Function], } } + inputId="name" + isClearable={false} + isSearchable={false} onChange={[Function]} - optionRenderer={[Function]} options={ Array [ Object { @@ -508,10 +507,7 @@ exports[`should render correctly: when there are configuration errors (non-admin }, ] } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} + value={Array []} /> </div> </div> @@ -617,19 +613,18 @@ exports[`should render correctly: with a single ALM instance 1`] = ` <div className="settings-definition-right" > - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={ + <Select + className="abs-width-400 big-spacer-top it__configuration-name-select" + components={ Object { - "maxWidth": "210%", - "width": "auto", + "Option": [Function], + "SingleValue": [Function], } } + inputId="name" + isClearable={false} + isSearchable={false} onChange={[Function]} - optionRenderer={[Function]} options={ Array [ Object { @@ -639,10 +634,7 @@ exports[`should render correctly: with a single ALM instance 1`] = ` }, ] } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} + value={Array []} /> </div> </div> @@ -699,19 +691,18 @@ exports[`should render correctly: with a valid and saved form 1`] = ` <div className="settings-definition-right" > - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={ + <Select + className="abs-width-400 big-spacer-top it__configuration-name-select" + components={ Object { - "maxWidth": "210%", - "width": "auto", + "Option": [Function], + "SingleValue": [Function], } } + inputId="name" + isClearable={false} + isSearchable={false} onChange={[Function]} - optionRenderer={[Function]} options={ Array [ Object { @@ -735,10 +726,15 @@ exports[`should render correctly: with a valid and saved form 1`] = ` }, ] } - searchable={false} - value="i1" - valueKey="key" - valueRenderer={[Function]} + value={ + Array [ + Object { + "alm": "github", + "key": "i1", + "url": "http://github.enterprise.com", + }, + ] + } /> </div> </div> @@ -857,19 +853,18 @@ exports[`should render correctly: with an empty form 1`] = ` <div className="settings-definition-right" > - <SelectLegacy - autosize={true} - className="abs-width-400 big-spacer-top" - clearable={false} - id="name" - menuContainerStyle={ + <Select + className="abs-width-400 big-spacer-top it__configuration-name-select" + components={ Object { - "maxWidth": "210%", - "width": "auto", + "Option": [Function], + "SingleValue": [Function], } } + inputId="name" + isClearable={false} + isSearchable={false} onChange={[Function]} - optionRenderer={[Function]} options={ Array [ Object { @@ -893,10 +888,7 @@ exports[`should render correctly: with an empty form 1`] = ` }, ] } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} + value={Array []} /> </div> </div> @@ -949,31 +941,3 @@ exports[`should render correctly: with no ALM instances (non-admin user) 1`] = ` </Alert> </div> `; - -exports[`should render select options correctly 1`] = ` -<React.Fragment> - <span> - i1 - — - </span> - <span - className="text-muted" - > - http://github.enterprise.com - </span> -</React.Fragment> -`; - -exports[`should render select options correctly 2`] = ` -<React.Fragment> - <span> - i2 - — - </span> - <span - className="text-muted" - > - http://github.enterprise.com - </span> -</React.Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx b/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx deleted file mode 100644 index 6b6cca17385..00000000000 --- a/server/sonar-web/src/main/js/apps/users/components/UsersSelectSearch.tsx +++ /dev/null @@ -1,197 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { debounce } from 'lodash'; -import * as React from 'react'; -import SelectLegacy from '../../../components/controls/SelectLegacy'; -import Avatar from '../../../components/ui/Avatar'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; - -interface Option { - login: string; - name: string; - avatar?: string; -} - -interface Props { - autoFocus?: boolean; - excludedUsers: string[]; - handleValueChange: (option: Option) => void; - searchUsers: (query: string, ps: number) => Promise<{ users: Option[] }>; - selectedUser?: Option; -} - -interface State { - isLoading: boolean; - search: string; - searchResult: Option[]; -} - -const LIST_SIZE = 10; -const AVATAR_SIZE = 16; - -export default class UsersSelectSearch extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - this.handleSearch = debounce(this.handleSearch, 250); - this.state = { searchResult: [], isLoading: false, search: '' }; - } - - componentDidMount() { - this.mounted = true; - this.handleSearch(this.state.search); - } - - componentDidUpdate(prevProps: Props) { - if (this.props.excludedUsers !== prevProps.excludedUsers) { - this.handleSearch(this.state.search); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - filterSearchResult = ({ users }: { users: Option[] }) => - users.filter(user => !this.props.excludedUsers.includes(user.login)).slice(0, LIST_SIZE); - - filterOptions = (options: Option[]) => { - return options; // We don't filter anything, this is done by the WS - }; - - handleSearch = (search: string) => { - this.props - .searchUsers(search, Math.min(this.props.excludedUsers.length + LIST_SIZE, 500)) - .then(this.filterSearchResult) - .then( - searchResult => { - if (this.mounted) { - this.setState({ isLoading: false, searchResult }); - } - }, - () => { - if (this.mounted) { - this.setState({ isLoading: false, searchResult: [] }); - } - } - ); - }; - - handleInputChange = (search: string) => { - if (search == null || search.length === 1) { - this.setState({ search }); - } else { - this.setState({ isLoading: true, search }); - this.handleSearch(search); - } - }; - - render() { - const noResult = - this.state.search.length === 1 - ? translateWithParameters('select2.tooShort', 2) - : translate('no_results'); - return ( - <SelectLegacy - autoFocus={this.props.autoFocus} - className="Select-big" - clearable={false} - filterOptions={this.filterOptions} - isLoading={this.state.isLoading} - labelKey="name" - noResultsText={noResult} - onChange={this.props.handleValueChange} - onInputChange={this.handleInputChange} - optionComponent={UsersSelectSearchOption} - options={this.state.searchResult} - placeholder="" - searchable={true} - value={this.props.selectedUser} - valueComponent={UsersSelectSearchValue} - valueKey="login" - /> - ); - } -} - -interface OptionProps { - children?: React.ReactNode; - className?: string; - isFocused?: boolean; - onFocus: (option: Option, evt: React.MouseEvent<HTMLDivElement>) => void; - onSelect: (option: Option, evt: React.MouseEvent<HTMLDivElement>) => void; - option: Option; -} - -export class UsersSelectSearchOption extends React.PureComponent<OptionProps> { - handleMouseDown = (evt: React.MouseEvent<HTMLDivElement>) => { - evt.preventDefault(); - evt.stopPropagation(); - this.props.onSelect(this.props.option, evt); - }; - - handleMouseEnter = (evt: React.MouseEvent<HTMLDivElement>) => { - this.props.onFocus(this.props.option, evt); - }; - - handleMouseMove = (evt: React.MouseEvent<HTMLDivElement>) => { - if (this.props.isFocused) { - return; - } - this.props.onFocus(this.props.option, evt); - }; - - render() { - const { option } = this.props; - return ( - <div - className={this.props.className} - onMouseDown={this.handleMouseDown} - onMouseEnter={this.handleMouseEnter} - onMouseMove={this.handleMouseMove} - role="listitem" - title={option.name}> - <Avatar hash={option.avatar} name={option.name} size={AVATAR_SIZE} /> - <strong className="spacer-left">{this.props.children}</strong> - <span className="note little-spacer-left">{option.login}</span> - </div> - ); - } -} - -interface ValueProps { - value?: Option; - children?: React.ReactNode; -} - -export function UsersSelectSearchValue({ children, value }: ValueProps) { - return ( - <div className="Select-value" title={value ? value.name : ''}> - {value && value.login && ( - <div className="Select-value-label"> - <Avatar hash={value.avatar} name={value.name} size={AVATAR_SIZE} /> - <strong className="spacer-left">{children}</strong> - <span className="note little-spacer-left">{value.login}</span> - </div> - )} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearch-test.tsx b/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearch-test.tsx deleted file mode 100644 index d1d2c35b689..00000000000 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/UsersSelectSearch-test.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import UsersSelectSearch, { - UsersSelectSearchOption, - UsersSelectSearchValue -} from '../UsersSelectSearch'; - -const selectedUser = { - login: 'admin', - name: 'Administrator', - avatar: '7daf6c79d4802916d83f6266e24850af' -}; -const users = [ - { login: 'admin', name: 'Administrator', email: 'admin@admin.ch' }, - { login: 'test', name: 'Tester', email: 'tester@testing.ch' }, - { login: 'foo', name: 'Foo Bar', email: 'foo@bar.ch' } -]; -const excludedUsers = ['admin']; - -describe('UsersSelectSearch', () => { - it('should render correctly', () => { - const onSearch = jest.fn().mockResolvedValue({ users }); - const wrapper = shallow( - <UsersSelectSearch - excludedUsers={excludedUsers} - handleValueChange={jest.fn()} - searchUsers={onSearch} - selectedUser={selectedUser} - /> - ); - expect(wrapper).toMatchSnapshot(); - const searchResult = (wrapper.instance() as UsersSelectSearch).filterSearchResult({ users }); - expect(searchResult).toMatchSnapshot(); - expect(wrapper.setState({ searchResult })).toMatchSnapshot(); - }); -}); - -describe('UsersSelectSearchOption', () => { - it('should render correctly without all parameters', () => { - const wrapper = shallow( - <UsersSelectSearchOption onFocus={jest.fn()} onSelect={jest.fn()} option={selectedUser}> - {selectedUser.name} - </UsersSelectSearchOption> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('should render correctly with email instead of hash', () => { - const wrapper = shallow( - <UsersSelectSearchOption onFocus={jest.fn()} onSelect={jest.fn()} option={users[0]}> - {users[0].name} - </UsersSelectSearchOption> - ); - expect(wrapper).toMatchSnapshot(); - }); -}); - -describe('UsersSelectSearchValue', () => { - it('should render correctly with a user', () => { - const wrapper = shallow( - <UsersSelectSearchValue value={selectedUser}>{selectedUser.name}</UsersSelectSearchValue> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('should render correctly with email instead of hash', () => { - const wrapper = shallow( - <UsersSelectSearchValue value={users[0]}>{users[0].name}</UsersSelectSearchValue> - ); - expect(wrapper).toMatchSnapshot(); - }); - - it('should render correctly without value', () => { - const wrapper = shallow(<UsersSelectSearchValue />); - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.tsx.snap b/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.tsx.snap deleted file mode 100644 index 0d6493812cf..00000000000 --- a/server/sonar-web/src/main/js/apps/users/components/__tests__/__snapshots__/UsersSelectSearch-test.tsx.snap +++ /dev/null @@ -1,192 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UsersSelectSearch should render correctly 1`] = ` -<SelectLegacy - className="Select-big" - clearable={false} - filterOptions={[Function]} - isLoading={false} - labelKey="name" - noResultsText="no_results" - onChange={[MockFunction]} - onInputChange={[Function]} - optionComponent={[Function]} - options={Array []} - placeholder="" - searchable={true} - value={ - Object { - "avatar": "7daf6c79d4802916d83f6266e24850af", - "login": "admin", - "name": "Administrator", - } - } - valueComponent={[Function]} - valueKey="login" -/> -`; - -exports[`UsersSelectSearch should render correctly 2`] = ` -Array [ - Object { - "email": "tester@testing.ch", - "login": "test", - "name": "Tester", - }, - Object { - "email": "foo@bar.ch", - "login": "foo", - "name": "Foo Bar", - }, -] -`; - -exports[`UsersSelectSearch should render correctly 3`] = ` -<SelectLegacy - className="Select-big" - clearable={false} - filterOptions={[Function]} - isLoading={false} - labelKey="name" - noResultsText="no_results" - onChange={[MockFunction]} - onInputChange={[Function]} - optionComponent={[Function]} - options={ - Array [ - Object { - "email": "tester@testing.ch", - "login": "test", - "name": "Tester", - }, - Object { - "email": "foo@bar.ch", - "login": "foo", - "name": "Foo Bar", - }, - ] - } - placeholder="" - searchable={true} - value={ - Object { - "avatar": "7daf6c79d4802916d83f6266e24850af", - "login": "admin", - "name": "Administrator", - } - } - valueComponent={[Function]} - valueKey="login" -/> -`; - -exports[`UsersSelectSearchOption should render correctly with email instead of hash 1`] = ` -<div - onMouseDown={[Function]} - onMouseEnter={[Function]} - onMouseMove={[Function]} - role="listitem" - title="Administrator" -> - <withAppStateContext(Avatar) - name="Administrator" - size={16} - /> - <strong - className="spacer-left" - > - Administrator - </strong> - <span - className="note little-spacer-left" - > - admin - </span> -</div> -`; - -exports[`UsersSelectSearchOption should render correctly without all parameters 1`] = ` -<div - onMouseDown={[Function]} - onMouseEnter={[Function]} - onMouseMove={[Function]} - role="listitem" - title="Administrator" -> - <withAppStateContext(Avatar) - hash="7daf6c79d4802916d83f6266e24850af" - name="Administrator" - size={16} - /> - <strong - className="spacer-left" - > - Administrator - </strong> - <span - className="note little-spacer-left" - > - admin - </span> -</div> -`; - -exports[`UsersSelectSearchValue should render correctly with a user 1`] = ` -<div - className="Select-value" - title="Administrator" -> - <div - className="Select-value-label" - > - <withAppStateContext(Avatar) - hash="7daf6c79d4802916d83f6266e24850af" - name="Administrator" - size={16} - /> - <strong - className="spacer-left" - > - Administrator - </strong> - <span - className="note little-spacer-left" - > - admin - </span> - </div> -</div> -`; - -exports[`UsersSelectSearchValue should render correctly with email instead of hash 1`] = ` -<div - className="Select-value" - title="Administrator" -> - <div - className="Select-value-label" - > - <withAppStateContext(Avatar) - name="Administrator" - size={16} - /> - <strong - className="spacer-left" - > - Administrator - </strong> - <span - className="note little-spacer-left" - > - admin - </span> - </div> -</div> -`; - -exports[`UsersSelectSearchValue should render correctly without value 1`] = ` -<div - className="Select-value" - title="" -/> -`; |