diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2023-03-01 13:32:28 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-03-02 20:03:50 +0000 |
commit | 58abe872d6edb1ec16b2e78dc5d952085be64f0d (patch) | |
tree | 8c1d8cf56b9be689777b93ea4abb180351569e69 /server/sonar-web | |
parent | 019be73fb00148ee912ed827284f63c33df9b95a (diff) | |
download | sonarqube-58abe872d6edb1ec16b2e78dc5d952085be64f0d.tar.gz sonarqube-58abe872d6edb1ec16b2e78dc5d952085be64f0d.zip |
SONAR-18580 add RTL for settings (part1)
Diffstat (limited to 'server/sonar-web')
42 files changed, 530 insertions, 3248 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts index ac36f1448f1..abfd964dcb8 100644 --- a/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts @@ -19,57 +19,164 @@ */ import { cloneDeep } from 'lodash'; import { HousekeepingPolicy } from '../../apps/audit-logs/utils'; +import { mockDefinition } from '../../helpers/mocks/settings'; import { BranchParameters } from '../../types/branch-like'; -import { SettingsKey, SettingValue } from '../../types/settings'; -import { getAllValues, getValue, getValues } from '../settings'; +import { + ExtendedSettingDefinition, + SettingDefinition, + SettingsKey, + SettingType, + SettingValue, +} from '../../types/settings'; +import { + getAllValues, + getDefinitions, + getValue, + getValues, + resetSettingValue, + setSettingValue, +} from '../settings'; + +const isEmptyString = (i: string) => i.trim() === ''; + +export const DEFAULT_DEFINITIONS_MOCK = [ + mockDefinition({ + category: 'general', + key: 'sonar.announcement.message', + subCategory: 'Announcement', + name: 'Announcement message', + description: 'Enter message', + defaultValue: '.js,.jsx,.cjs,.vue,.mjs', + type: SettingType.TEXT, + }), + mockDefinition({ + category: 'general', + key: 'sonar.ce.parallelProjectTasks', + subCategory: 'Compute Engine', + name: 'Run analysis in paralel', + description: 'Enter message', + defaultValue: '.js,.jsx,.cjs,.vue,.mjs', + type: SettingType.TEXT, + }), + mockDefinition({ + category: 'javascript', + key: 'sonar.javascript.globals', + subCategory: 'General', + name: 'Global Variables', + description: 'List of Global variables', + multiValues: true, + defaultValue: 'angular,google,d3', + }), + mockDefinition({ + category: 'javascript', + key: 'sonar.javascript.file.suffixes', + subCategory: 'General', + name: 'JavaScript File Suffixes', + description: 'List of suffixes for files to analyze', + multiValues: true, + defaultValue: '.js,.jsx,.cjs,.vue,.mjs', + }), + mockDefinition({ + category: 'External Analyzers', + key: 'sonar.androidLint.reportPaths', + subCategory: 'Android', + name: 'Android Lint Report Files', + description: 'Paths to xml files', + multiValues: true, + }), +]; export default class SettingsServiceMock { - settingValues: SettingValue[]; - defaultValues: SettingValue[] = [ + #defaultValues: SettingValue[] = [ { key: SettingsKey.AuditHouseKeeping, value: HousekeepingPolicy.Weekly, }, + { + key: 'sonar.javascript.globals', + values: ['angular', 'google', 'd3'], + }, ]; + #settingValues: SettingValue[] = cloneDeep(this.#defaultValues); + + #definitions: ExtendedSettingDefinition[] = cloneDeep(DEFAULT_DEFINITIONS_MOCK); + constructor() { - this.settingValues = cloneDeep(this.defaultValues); - (getValue as jest.Mock).mockImplementation(this.handleGetValue); - (getValues as jest.Mock).mockImplementation(this.handleGetValues); - (getAllValues as jest.Mock).mockImplementation(this.handleGetAllValues); + jest.mocked(getDefinitions).mockImplementation(this.handleGetDefinitions); + jest.mocked(getValue).mockImplementation(this.handleGetValue); + jest.mocked(getValues).mockImplementation(this.handleGetValues); + jest.mocked(getAllValues).mockImplementation(this.handleGetAllValues); + jest.mocked(setSettingValue).mockImplementation(this.handleSetSettingValue); + jest.mocked(resetSettingValue).mockImplementation(this.handleResetSettingValue); } handleGetValue = (data: { key: string; component?: string } & BranchParameters) => { - const setting = this.settingValues.find((s) => s.key === data.key); + const setting = this.#settingValues.find((s) => s.key === data.key) as SettingValue; return this.reply(setting); }; handleGetValues = (data: { keys: string[]; component?: string } & BranchParameters) => { - const settings = this.settingValues.filter((s) => data.keys.includes(s.key)); + const settings = this.#settingValues.filter((s) => data.keys.includes(s.key)); return this.reply(settings); }; handleGetAllValues = () => { - return this.reply(this.settingValues); + return this.reply(this.#settingValues); + }; + + handleGetDefinitions = () => { + return this.reply(this.#definitions); + }; + + handleSetSettingValue = (definition: SettingDefinition, value: any): Promise<void> => { + if ( + (typeof value === 'string' && isEmptyString(value)) || + (value instanceof Array && value.some(isEmptyString)) + ) { + throw new ResponseError('validation error', { + errors: [{ msg: 'A non empty value must be provided' }], + }); + } + + this.set(definition.key, value); + + return this.reply(undefined); + }; + + handleResetSettingValue = (data: { keys: string; component?: string } & BranchParameters) => { + const setting = this.#settingValues.find((s) => s.key === data.keys) as SettingValue; + const definition = this.#definitions.find( + (d) => d.key === data.keys + ) as ExtendedSettingDefinition; + if (definition.multiValues === true) { + setting.values = definition.defaultValue?.split(',') ?? []; + } else { + setting.value = definition.defaultValue ?? ''; + } + + return this.reply(undefined); }; emptySettings = () => { - this.settingValues = []; + this.#settingValues = []; return this; }; - set = (key: SettingsKey, value: string) => { - const setting = this.settingValues.find((s) => s.key === key); + set = (key: string | SettingsKey, value: any) => { + const setting = this.#settingValues.find((s) => s.key === key); if (setting) { setting.value = value; + setting.values = value; } else { - this.settingValues.push({ key, value }); + this.#settingValues.push({ key, value }); } return this; }; reset = () => { - this.settingValues = cloneDeep(this.defaultValues); + this.#settingValues = cloneDeep(this.#defaultValues); + this.#definitions = cloneDeep(DEFAULT_DEFINITIONS_MOCK); return this; }; @@ -77,3 +184,13 @@ export default class SettingsServiceMock { return Promise.resolve(cloneDeep(response)); } } + +class ResponseError extends Error { + #response: any; + constructor(name: string, response: any) { + super(name); + this.#response = response; + } + + json = () => Promise.resolve(this.#response); +} diff --git a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts index fde31d3de76..c3643ff8af2 100644 --- a/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { hasMessage } from '../../../helpers/l10n'; import { mockComponent } from '../../../helpers/mocks/component'; import { mockDefinition, mockSettingValue } from '../../../helpers/mocks/settings'; import { @@ -25,7 +26,18 @@ import { SettingFieldDefinition, SettingType, } from '../../../types/settings'; -import { buildSettingLink, getDefaultValue, getEmptyValue, getSettingValue } from '../utils'; +import { + buildSettingLink, + getDefaultValue, + getEmptyValue, + getPropertyName, + getSettingValue, +} from '../utils'; + +jest.mock('../../../helpers/l10n', () => ({ + ...jest.requireActual('../../../helpers/l10n'), + hasMessage: jest.fn(), +})); const fields = [ { key: 'foo', type: 'STRING' } as SettingFieldDefinition, @@ -40,6 +52,21 @@ const settingDefinition: ExtendedSettingDefinition = { subCategory: 'subtest', }; +describe('getPropertyName', () => { + it('should return correct value for existing translation', () => { + jest.mocked(hasMessage).mockImplementation(() => true); + expect(getPropertyName(mockDefinition())).toBe('property.foo.name'); + }); + + it('should return a name when translation doesn`t exist', () => { + jest.mocked(hasMessage).mockImplementation(() => false); + expect(getPropertyName(mockDefinition({ name: 'nicename', key: 'keey' }))).toBe('nicename'); + }); + it('should return a key when translation and name dont exist', () => { + expect(getPropertyName(mockDefinition({ key: 'keey' }))).toBe('keey'); + }); +}); + describe('#getEmptyValue()', () => { it('should work for property sets', () => { const setting: ExtendedSettingDefinition = { diff --git a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx index a9e7f6f89f7..a039b597626 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx @@ -38,7 +38,7 @@ export interface CategoriesListProps extends WithAvailableFeaturesProps { selectedCategory: string; } -export function CategoriesList(props: CategoriesListProps) { +function CategoriesList(props: CategoriesListProps) { const { categories, component, defaultCategory, selectedCategory } = props; const categoriesWithName = categories diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx index 22130b7493d..f90f0072f7c 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx @@ -20,9 +20,9 @@ import * as React from 'react'; import { Button, ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; import Modal from '../../../components/controls/Modal'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Setting } from '../../../types/settings'; -import { getDefaultValue, isEmptyValue } from '../utils'; +import { getDefaultValue, getPropertyName, isEmptyValue } from '../utils'; type Props = { changedValue?: string; @@ -100,7 +100,14 @@ export default class DefinitionActions extends React.PureComponent<Props, State> )} {showReset && ( - <Button className="spacer-right" onClick={this.handleReset}> + <Button + className="spacer-right" + aria-label={translateWithParameters( + 'settings.definition.reset', + getPropertyName(setting.definition) + )} + onClick={this.handleReset} + > {translate('reset_verb')} </Button> )} diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx index f9d2306f11e..e05ce398856 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx @@ -40,7 +40,7 @@ interface State { loading: boolean; } -export class SettingsApp extends React.PureComponent<Props, State> { +class SettingsApp extends React.PureComponent<Props, State> { mounted = false; state: State = { definitions: [], loading: true }; diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsAppRenderer.tsx index 9d844cc2133..7bc291da1a8 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SettingsAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsAppRenderer.tsx @@ -40,7 +40,7 @@ export interface SettingsAppRendererProps { location: Location; } -export function SettingsAppRenderer(props: SettingsAppRendererProps) { +function SettingsAppRenderer(props: SettingsAppRendererProps) { const { definitions, component, loading, location } = props; const categories = React.useMemo(() => { @@ -92,7 +92,7 @@ export function SettingsAppRenderer(props: SettingsAppRendererProps) { <div className="layout-page-main-inner"> {/* Adding a key to force re-rendering of the category content, so that it resets the scroll position */} <div className="big-padded" key={selectedCategory}> - {foundAdditionalCategory && shouldRenderAdditionalCategory ? ( + {shouldRenderAdditionalCategory ? ( foundAdditionalCategory.renderComponent({ categories, component, diff --git a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx index f79861d91bf..c1cfa10fca9 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx @@ -69,7 +69,11 @@ export default function SettingsSearchRenderer(props: SettingsSearchRendererProp /> {showResults && ( <DropdownOverlay noPadding={true}> - <ul className="settings-search-results menu" ref={scrollableNodeRef}> + <ul + className="settings-search-results menu" + title={translate('settings.search.results')} + ref={scrollableNodeRef} + > {results && results.length > 0 ? ( results.map((r) => ( <li diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx index b904584590f..697791cc20d 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx @@ -36,7 +36,7 @@ export interface SubCategoryDefinitionsListProps { displaySubCategoryTitle?: boolean; } -export class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefinitionsListProps> { +class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefinitionsListProps> { componentDidUpdate(prevProps: SubCategoryDefinitionsListProps) { const { hash } = this.props.location; if (hash && prevProps.location.hash !== hash) { diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx deleted file mode 100644 index a9977f29685..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx +++ /dev/null @@ -1,52 +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 { find } from 'lodash'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../../constants'; -import { ADDITIONAL_CATEGORIES } from '../AdditionalCategories'; - -it('should render additional categories component correctly', () => { - ADDITIONAL_CATEGORIES.forEach((cat) => { - expect( - cat.renderComponent({ - categories: [], - component: mockComponent(), - definitions: [], - selectedCategory: 'TEST', - }) - ).toMatchSnapshot(); - }); -}); - -it('should not render pull request decoration binding component when the component is not defined', () => { - const category = find( - ADDITIONAL_CATEGORIES, - (c) => c.key === PULL_REQUEST_DECORATION_BINDING_CATEGORY - ); - - expect( - category!.renderComponent({ - categories: [], - component: undefined, - definitions: [], - selectedCategory: '', - }) - ).toBeUndefined(); -}); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx index e66cafe8d49..f860ebab0ab 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { mockComponent } from '../../../../helpers/mocks/component'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import { AdditionalCategory } from '../AdditionalCategories'; -import { CategoriesList, CategoriesListProps } from '../AllCategoriesList'; +import CategoriesList, { CategoriesListProps } from '../AllCategoriesList'; jest.mock('../AdditionalCategories', () => ({ ADDITIONAL_CATEGORIES: [ diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx deleted file mode 100644 index c7ad9c69876..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx +++ /dev/null @@ -1,38 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { AnalysisScope } from '../AnalysisScope'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -function shallowRender() { - return shallow( - <AnalysisScope - categories={[]} - component={mockComponent()} - definitions={[]} - selectedCategory="TEST" - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/CategoryDefinitionsList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/CategoryDefinitionsList-test.tsx deleted file mode 100644 index 926c874e2b6..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/CategoryDefinitionsList-test.tsx +++ /dev/null @@ -1,79 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { getValues } from '../../../../api/settings'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockDefinition, mockSettingValue } from '../../../../helpers/mocks/settings'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import CategoryDefinitionsList from '../CategoryDefinitionsList'; - -jest.mock('../../../../api/settings', () => ({ - getValues: jest.fn().mockResolvedValue([]), -})); - -it('should load settings values', async () => { - const settings = [mockSettingValue({ key: 'yes' }), mockSettingValue({ key: 'yesagain' })]; - (getValues as jest.Mock).mockResolvedValueOnce(settings); - - const definitions = [ - mockDefinition({ category: 'general', key: 'yes' }), - mockDefinition({ category: 'other', key: 'nope' }), - mockDefinition({ category: 'general', key: 'yesagain' }), - ]; - - const wrapper = shallowRender({ - definitions, - }); - - await waitAndUpdate(wrapper); - - expect(getValues).toHaveBeenCalledWith({ keys: ['yes', 'yesagain'], component: undefined }); - - expect(wrapper.state().settings).toEqual([ - { definition: definitions[0], settingValue: settings[0] }, - { definition: definitions[2], settingValue: settings[1] }, - ]); -}); - -it('should reload on category change', async () => { - const definitions = [ - mockDefinition({ category: 'general', key: 'yes' }), - mockDefinition({ category: 'other', key: 'nope' }), - mockDefinition({ category: 'general', key: 'yesagain' }), - ]; - const wrapper = shallowRender({ component: mockComponent({ key: 'comp-key' }), definitions }); - - await waitAndUpdate(wrapper); - - expect(getValues).toHaveBeenCalledWith({ keys: ['yes', 'yesagain'], component: 'comp-key' }); - - wrapper.setProps({ category: 'other' }); - - await waitAndUpdate(wrapper); - - expect(getValues).toHaveBeenCalledWith({ keys: ['nope'], component: 'comp-key' }); -}); - -function shallowRender(props: Partial<CategoryDefinitionsList['props']> = {}) { - return shallow<CategoryDefinitionsList>( - <CategoryDefinitionsList category="general" definitions={[]} {...props} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx index 40869c50470..40869c50470 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx deleted file mode 100644 index d542b9a7030..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx +++ /dev/null @@ -1,87 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { ExtendedSettingDefinition, SettingType } from '../../../../types/settings'; -import DefinitionActions from '../DefinitionActions'; - -const definition: ExtendedSettingDefinition = { - category: 'baz', - description: 'lorem', - fields: [], - key: 'key', - name: 'foobar', - options: [], - subCategory: 'bar', - type: SettingType.STRING, -}; - -const settings = { - key: 'key', - hasValue: true, - definition, - value: 'baz', -}; - -it('displays default message when value is default', () => { - const wrapper = shallowRender('', false, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays save button when it can be saved', () => { - const wrapper = shallowRender('foo', false, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays cancel button when value changed and no error', () => { - const wrapper = shallowRender('foo', false, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays cancel button when value changed and has error', () => { - const wrapper = shallowRender('foo', true, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('disables save button on error', () => { - const wrapper = shallowRender('foo', true, true); - expect(wrapper).toMatchSnapshot(); -}); - -it('displays reset button when empty and not default', () => { - const wrapper = shallowRender('', true, false); - expect(wrapper).toMatchSnapshot(); -}); - -function shallowRender(changedValue: string, hasError: boolean, isDefault: boolean) { - return shallow( - <DefinitionActions - isEditing={false} - changedValue={changedValue} - hasError={hasError} - hasValueChanged={changedValue !== ''} - isDefault={isDefault} - onCancel={jest.fn()} - onReset={jest.fn()} - onSave={jest.fn()} - setting={settings} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionRenderer-test.tsx deleted file mode 100644 index 0010f95432d..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionRenderer-test.tsx +++ /dev/null @@ -1,58 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockDefinition, mockSettingValue } from '../../../../helpers/mocks/settings'; -import DefinitionRenderer, { DefinitionRendererProps } from '../DefinitionRenderer'; - -it('should render correctly', () => { - expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); - expect( - shallowRender({ definition: mockDefinition({ description: 'description' }) }) - ).toMatchSnapshot('with description'); - expect( - shallowRender({ - validationMessage: 'validation message', - }) - ).toMatchSnapshot('in error'); - expect(shallowRender({ success: true })).toMatchSnapshot('success'); - expect( - shallowRender({ settingValue: mockSettingValue({ key: 'foo', value: 'original value' }) }) - ).toMatchSnapshot('original value'); - - expect(shallowRender({ changedValue: 'new value' })).toMatchSnapshot('changed value'); -}); - -function shallowRender(props: Partial<DefinitionRendererProps> = {}) { - return shallow<DefinitionRendererProps>( - <DefinitionRenderer - isEditing={false} - definition={mockDefinition()} - loading={false} - onCancel={jest.fn()} - onChange={jest.fn()} - onEditing={jest.fn()} - onReset={jest.fn()} - onSave={jest.fn()} - success={false} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx deleted file mode 100644 index c0ccbc0a228..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx +++ /dev/null @@ -1,90 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { sendTestEmail } from '../../../../api/settings'; -import { mockLoggedInUser } from '../../../../helpers/testMocks'; -import { change, submit, waitAndUpdate } from '../../../../helpers/testUtils'; -import { EmailForm } from '../EmailForm'; - -jest.mock('../../../../helpers/request', () => ({ - parseError: jest.fn().mockResolvedValue('Error message'), -})); - -jest.mock('../../../../api/settings', () => ({ - sendTestEmail: jest.fn().mockResolvedValue(null), -})); - -it('should render correctly', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot('default'); - wrapper.setState({ loading: true }); - expect(wrapper).toMatchSnapshot('sending'); - wrapper.setState({ loading: false, success: 'email@example.com' }); - expect(wrapper).toMatchSnapshot('success'); - wrapper.setState({ success: undefined, error: 'Some error message' }); - expect(wrapper).toMatchSnapshot('error'); -}); - -it('should correctly control the inputs', () => { - const wrapper = shallowRender(); - - change(wrapper.find('#test-email-to'), 'new@recipient.com'); - expect(wrapper.state().recipient).toBe('new@recipient.com'); - - change(wrapper.find('#test-email-subject'), 'New subject'); - expect(wrapper.state().subject).toBe('New subject'); - - change(wrapper.find('#test-email-message'), 'New message'); - expect(wrapper.state().message).toBe('New message'); -}); - -it('should correctly test the email sending', async () => { - const wrapper = shallowRender(); - - submit(wrapper.find('form')); - expect(sendTestEmail).toHaveBeenCalledWith( - 'luke@skywalker.biz', - 'email_configuration.test.subject', - 'email_configuration.test.message_text' - ); - expect(wrapper.state().loading).toBe(true); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().loading).toBe(false); - expect(wrapper.state().error).toBeUndefined(); - expect(wrapper.state().success).toBe('luke@skywalker.biz'); - - (sendTestEmail as jest.Mock).mockRejectedValueOnce(null); - - submit(wrapper.find('form')); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().success).toBeUndefined(); - expect(wrapper.state().error).toBe('Error message'); -}); - -function shallowRender(props: Partial<EmailForm['props']> = {}) { - return shallow<EmailForm>( - <EmailForm currentUser={mockLoggedInUser({ email: 'luke@skywalker.biz' })} {...props} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-it.tsx new file mode 100644 index 00000000000..9e0868ebbf4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-it.tsx @@ -0,0 +1,132 @@ +/* + * 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 userEvent from '@testing-library/user-event'; +import { last } from 'lodash'; +import React from 'react'; +import { byRole, byText } from 'testing-library-selector'; +import SettingsServiceMock, { + DEFAULT_DEFINITIONS_MOCK, +} from '../../../../api/mocks/SettingsServiceMock'; +import { mockComponent } from '../../../../helpers/mocks/component'; +import { renderApp } from '../../../../helpers/testReactTestingUtils'; +import { AdditionalCategoryComponentProps } from '../AdditionalCategories'; +import Languages from '../Languages'; + +jest.mock('../../../../api/settings'); + +let settingsMock: SettingsServiceMock; + +beforeAll(() => { + settingsMock = new SettingsServiceMock(); +}); + +afterEach(() => { + settingsMock.reset(); +}); + +beforeEach(jest.clearAllMocks); + +const ui = { + languagesHeading: byRole('heading', { name: 'property.category.languages' }), + languagesSelect: byRole('combobox', { name: 'property.category.languages' }), + jsGeneralSubCategoryHeading: byRole('heading', { name: 'property.category.javascript.General' }), + jsGlobalVariablesHeading: byRole('heading', { + name: 'property.sonar.javascript.globals.name', + }), + jsGlobalVariablesDescription: byText('List of Global variables'), + jsFileSuffixesHeading: byRole('heading', { + name: 'property.sonar.javascript.file.suffixes.name', + }), + jsGlobalVariablesInput: byRole('textbox', { name: 'property.sonar.javascript.globals.name' }), + jsResetGlobalVariablesButton: byRole('button', { + name: 'settings.definition.reset.property.sonar.javascript.globals.name', + }), + + validationMsg: byText('settings.state.validation_failed.A non empty value must be provided'), + saveButton: byRole('button', { name: 'save' }), + cancelButton: byRole('button', { name: 'cancel' }), + resetButton: byRole('button', { name: 'reset_verb' }), +}; + +it('renders Language with selected Javascript category', async () => { + renderLanguages({ selectedCategory: 'javascript' }); + + expect(await ui.languagesHeading.find()).toBeInTheDocument(); + expect(await ui.jsGeneralSubCategoryHeading.find()).toBeInTheDocument(); + expect(ui.jsGlobalVariablesHeading.get()).toBeInTheDocument(); + expect(ui.jsFileSuffixesHeading.get()).toBeInTheDocument(); +}); + +it('render Language without definitions', async () => { + renderLanguages({ selectedCategory: 'niceLanguage' }); + + expect(await ui.languagesHeading.find()).toBeInTheDocument(); + expect(screen.queryByText(/niceLanguage/)).not.toBeInTheDocument(); +}); + +it('can save/reset/cancel or see error for custom mocked multi values definition Global Variables', async () => { + const user = userEvent.setup(); + renderLanguages({ selectedCategory: 'javascript' }); + + const jsVarsInputs = await ui.jsGlobalVariablesInput.findAll(); + const lastInput = last(jsVarsInputs); + // Adding new js variable (multi-values input) + expect(jsVarsInputs).toHaveLength(4); + + // Should see a validation message on typing empty string + await user.type(lastInput as HTMLElement, ' '); + await user.click(await ui.saveButton.find()); + expect(await ui.validationMsg.find()).toBeInTheDocument(); + + // Should save variable + await user.type(lastInput as HTMLElement, 'Testing'); + await user.click(await ui.saveButton.find()); + expect(ui.validationMsg.query()).not.toBeInTheDocument(); + expect(lastInput).toHaveValue(' Testing'); + + // Should reset to previous state on clicking cancel + await user.type(last(ui.jsGlobalVariablesInput.getAll()) as HTMLElement, 'Testing2'); + await user.click(ui.cancelButton.get()); + expect(last(ui.jsGlobalVariablesInput.getAll())).not.toHaveValue('Testing2'); + + // Clicking reset opens dialog and reset to default on confirm + const defaultValues = ['angular', 'google', 'd3', '']; + await user.click(ui.jsResetGlobalVariablesButton.get()); + await user.click(ui.resetButton.get()); + const newInputs = ui.jsGlobalVariablesInput.getAll(); + defaultValues.forEach((value, index) => expect(newInputs[index]).toHaveValue(value)); +}); + +function renderLanguages( + overrides: Partial<AdditionalCategoryComponentProps> = {}, + component = mockComponent() +) { + return renderApp( + '/', + <Languages + definitions={DEFAULT_DEFINITIONS_MOCK} + component={component} + categories={['javascript', 'sjava']} + selectedCategory="" + {...overrides} + /> + ); +} 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 deleted file mode 100644 index 2a54262416e..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx +++ /dev/null @@ -1,68 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import Select from '../../../../components/controls/Select'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; -import CategoryDefinitionsList from '../CategoryDefinitionsList'; -import { Languages, LanguagesProps } from '../Languages'; - -it('should render correctly', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render correctly with an unknow language', () => { - const wrapper = shallowRender({ selectedCategory: 'unknown' }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly handle a change of the selected language', () => { - const push = jest.fn(); - const router = mockRouter({ push }); - const wrapper = shallowRender({ router }); - expect(wrapper.find(CategoryDefinitionsList).props().category).toBe('java'); - - const { onChange } = wrapper.find(Select).props(); - - onChange({ label: '', originalValue: 'CoBoL', value: 'cobol' }); - expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } })); -}); - -it('should correctly show the subcategory for a component', () => { - const component = mockComponent(); - const wrapper = shallowRender({ component }); - expect(wrapper.find(CategoryDefinitionsList).props().component).toBe(component); -}); - -function shallowRender(props: Partial<LanguagesProps> = {}) { - return shallow( - <Languages - categories={['Java', 'JavaScript', 'COBOL']} - component={undefined} - definitions={[]} - location={mockLocation()} - router={mockRouter()} - selectedCategory="java" - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx index c95f2e18bb8..c95f2e18bb8 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx deleted file mode 100644 index 41843017237..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx +++ /dev/null @@ -1,32 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import PageHeader, { PageHeaderProps } from '../PageHeader'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('global'); - expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('for project'); -}); - -function shallowRender(props: Partial<PageHeaderProps> = {}) { - return shallow<PageHeaderProps>(<PageHeader definitions={[]} {...props} />); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx new file mode 100644 index 00000000000..c4a7d2582f7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx @@ -0,0 +1,191 @@ +/* + * 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 { within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Route } from 'react-router-dom'; +import selectEvent from 'react-select-event'; +import { byRole } from 'testing-library-selector'; +import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock'; +import { KeyboardKeys } from '../../../../helpers/keycodes'; +import { mockComponent } from '../../../../helpers/mocks/component'; +import { + renderAppRoutes, + renderAppWithComponentContext, + RenderContext, +} from '../../../../helpers/testReactTestingUtils'; +import { Feature } from '../../../../types/features'; +import { Component } from '../../../../types/types'; +import routes from '../../routes'; + +jest.mock('../../../../api/settings'); + +let settingsMock: SettingsServiceMock; + +beforeAll(() => { + settingsMock = new SettingsServiceMock(); +}); + +afterEach(() => { + settingsMock.reset(); +}); + +beforeEach(jest.clearAllMocks); + +const ui = { + categoryLink: (category: string) => byRole('link', { name: category }), + announcementHeading: byRole('heading', { name: 'property.category.general.Announcement' }), + + languagesHeading: byRole('heading', { name: 'property.category.languages' }), + languagesSelect: byRole('combobox', { name: 'property.category.languages' }), + jsGeneralSubCategoryHeading: byRole('heading', { name: 'property.category.javascript.General' }), + + settingsSearchInput: byRole('searchbox', { name: 'settings.search.placeholder' }), + searchList: byRole('list', { name: 'settings.search.results' }), + searchItem: (key: string) => byRole('link', { name: new RegExp(key) }), + searchClear: byRole('button', { name: 'clear' }), + + externalAnalyzersAndroidHeading: byRole('heading', { + name: 'property.category.External Analyzers.Android', + }), + generalComputeEngineHeading: byRole('heading', { + name: 'property.category.general.Compute Engine', + }), +}; + +describe('Global Settings', () => { + it('renders categories list and definitions', async () => { + const user = userEvent.setup(); + renderSettingsApp(); + + const globalCategories = [ + 'property.category.general', + 'property.category.languages', + 'property.category.External Analyzers', + 'settings.new_code_period.category', + 'property.category.almintegration', + ]; + + expect(await ui.categoryLink(globalCategories[0]).find()).toBeInTheDocument(); + globalCategories.forEach((name) => { + expect(ui.categoryLink(name).get()).toBeInTheDocument(); + }); + + expect(await ui.announcementHeading.find()).toBeInTheDocument(); + + // Navigating to Languages category + await user.click(await ui.categoryLink('property.category.languages').find()); + expect(await ui.languagesHeading.find()).toBeInTheDocument(); + }); + + it('renders Language category and can select any language', async () => { + const user = userEvent.setup(); + renderSettingsApp(); + + // Navigating to Languages category + await user.click(await ui.categoryLink('property.category.languages').find()); + expect(await ui.languagesHeading.find()).toBeInTheDocument(); + + await selectEvent.select(ui.languagesSelect.get(), 'property.category.javascript'); + expect(await ui.jsGeneralSubCategoryHeading.find()).toBeInTheDocument(); + }); + + it('can search definitions by name or key', async () => { + const user = userEvent.setup(); + renderSettingsApp(); + + expect(await ui.settingsSearchInput.find()).toBeInTheDocument(); + + // List popup should be closed if input is empty + await user.click(ui.settingsSearchInput.get()); + expect(ui.searchList.query()).not.toBeInTheDocument(); + + // Should shot 'no results' based on input value + await user.type(ui.settingsSearchInput.get(), 'asdjasnd'); + expect(ui.searchList.get()).toBeInTheDocument(); + expect(within(ui.searchList.get()).getByText('no_results')).toBeInTheDocument(); + await user.click(ui.searchClear.get()); + + // Should show results based on input value + const searchResultsKeys = [ + 'sonar.announcement.message', + 'sonar.ce.parallelProjectTasks', + 'sonar.androidLint.reportPaths', + ]; + + await user.type(ui.settingsSearchInput.get(), 'an'); + searchResultsKeys.forEach((key) => expect(ui.searchItem(key).get()).toBeInTheDocument()); + expect(ui.searchItem('sonar.javascript.globals').query()).not.toBeInTheDocument(); + + // Navigating through keyboard + await user.keyboard(`{${KeyboardKeys.DownArrow}}`); + await user.keyboard(`{${KeyboardKeys.UpArrow}}`); + await user.keyboard(`{${KeyboardKeys.DownArrow}}`); + await user.keyboard(`{${KeyboardKeys.Enter}}`); + + expect(await ui.externalAnalyzersAndroidHeading.find()).toBeInTheDocument(); + + // Navigating through link + await user.click(ui.searchClear.get()); + await user.type(ui.settingsSearchInput.get(), 'an'); + await user.click(ui.searchItem(searchResultsKeys[1]).get()); + + expect(await ui.generalComputeEngineHeading.find()).toBeInTheDocument(); + }); +}); + +describe('Project Settings', () => { + it('renders categories list and definitions', async () => { + const user = userEvent.setup(); + renderSettingsApp(mockComponent(), { featureList: [Feature.BranchSupport] }); + + const projectCategories = [ + 'property.category.general', + 'property.category.languages', + 'property.category.External Analyzers', + 'settings.pr_decoration.binding.category', + ]; + + expect(await ui.categoryLink(projectCategories[0]).find()).toBeInTheDocument(); + projectCategories.forEach((name) => { + expect(ui.categoryLink(name).get()).toBeInTheDocument(); + }); + + expect(await ui.announcementHeading.find()).toBeInTheDocument(); + + // Navigating to Languages category + await user.click(await ui.categoryLink('property.category.languages').find()); + expect(await ui.languagesHeading.find()).toBeInTheDocument(); + }); +}); + +function renderSettingsApp(component?: Component, context: RenderContext = {}) { + const path = component ? 'project' : 'admin'; + const wrapperRoutes = () => <Route path={path}>{routes()}</Route>; + const params: [string, typeof routes, RenderContext] = [ + `${path}/settings`, + wrapperRoutes, + context, + ]; + + return component + ? renderAppWithComponentContext(...params, { component }) + : renderAppRoutes(...params); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx deleted file mode 100644 index a4677e66bd5..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx +++ /dev/null @@ -1,71 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { getDefinitions } from '../../../../api/settings'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { - addSideBarClass, - addWhitePageClass, - removeSideBarClass, - removeWhitePageClass, -} from '../../../../helpers/pages'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { SettingsApp } from '../SettingsApp'; - -jest.mock('../../../../helpers/pages', () => ({ - addSideBarClass: jest.fn(), - addWhitePageClass: jest.fn(), - removeSideBarClass: jest.fn(), - removeWhitePageClass: jest.fn(), -})); - -jest.mock('../../../../api/settings', () => ({ - getDefinitions: jest.fn().mockResolvedValue([]), -})); - -it('should render default view correctly', async () => { - const wrapper = shallowRender(); - - expect(addSideBarClass).toHaveBeenCalled(); - expect(addWhitePageClass).toHaveBeenCalled(); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - - expect(getDefinitions).toHaveBeenCalledWith(undefined); - - wrapper.unmount(); - - expect(removeSideBarClass).toHaveBeenCalled(); - expect(removeWhitePageClass).toHaveBeenCalled(); -}); - -it('should fetch definitions for component', async () => { - const key = 'component-key'; - const wrapper = shallowRender({ component: mockComponent({ key }) }); - - await waitAndUpdate(wrapper); - expect(getDefinitions).toHaveBeenCalledWith(key); -}); - -function shallowRender(props: Partial<SettingsApp['props']> = {}) { - return shallow(<SettingsApp {...props} />); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsAppRenderer-test.tsx deleted file mode 100644 index e88fe230b01..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsAppRenderer-test.tsx +++ /dev/null @@ -1,69 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper'; -import { mockDefinition } from '../../../../helpers/mocks/settings'; -import { mockLocation } from '../../../../helpers/testMocks'; -import { - ALM_INTEGRATION_CATEGORY, - ANALYSIS_SCOPE_CATEGORY, - LANGUAGES_CATEGORY, - NEW_CODE_PERIOD_CATEGORY, - PULL_REQUEST_DECORATION_BINDING_CATEGORY, -} from '../../constants'; -import { SettingsAppRenderer, SettingsAppRendererProps } from '../SettingsAppRenderer'; - -it('should render loading correctly', () => { - expect(shallowRender({ loading: true }).type()).toBeNull(); -}); - -it('should render default view correctly', () => { - const wrapper = shallowRender(); - - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot('All Categories List'); -}); - -it.each([ - [NEW_CODE_PERIOD_CATEGORY], - [LANGUAGES_CATEGORY], - [ANALYSIS_SCOPE_CATEGORY], - [ALM_INTEGRATION_CATEGORY], - [PULL_REQUEST_DECORATION_BINDING_CATEGORY], -])('should render %s correctly', (category) => { - const wrapper = shallowRender({ - location: mockLocation({ query: { category } }), - }); - - expect(wrapper).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<SettingsAppRendererProps> = {}) { - const definitions = [mockDefinition(), mockDefinition({ key: 'bar', category: 'general' })]; - return shallow( - <SettingsAppRenderer - definitions={definitions} - loading={false} - location={mockLocation()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx deleted file mode 100644 index bc6705d11fe..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx +++ /dev/null @@ -1,154 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { KeyboardKeys } from '../../../../helpers/keycodes'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockDefinition } from '../../../../helpers/mocks/settings'; -import { mockRouter } from '../../../../helpers/testMocks'; -import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils'; -import { queryToSearch } from '../../../../helpers/urls'; -import { SettingsSearch } from '../SettingsSearch'; - -jest.mock('lunr', () => - jest.fn(() => ({ - search: jest.fn(() => [ - { - ref: 'foo', - }, - { - ref: 'sonar.new_code_period', - }, - ]), - })) -); - -describe('instance', () => { - const router = mockRouter(); - const wrapper = shallowRender({ router }); - - it('should build the index', () => { - expect(wrapper.instance().index).not.toBeNull(); - - const def = mockDefinition(); - expect(wrapper.instance().definitionsByKey).toEqual( - expect.objectContaining({ [def.key]: def }) - ); - }); - - it('should handle search', async () => { - wrapper.instance().handleSearchChange('query'); - - await waitAndUpdate(wrapper); - expect(wrapper.state().searchQuery).toBe('query'); - - expect(wrapper.instance().index.search).toHaveBeenCalled(); - expect(wrapper.state().showResults).toBe(true); - expect(wrapper.state().results).toHaveLength(2); - }); - - it('should handle empty search', async () => { - wrapper.instance().handleSearchChange(''); - - await waitAndUpdate(wrapper); - expect(wrapper.state().searchQuery).toBe(''); - - expect(wrapper.instance().index.search).toHaveBeenCalled(); - expect(wrapper.state().showResults).toBe(false); - }); - - it('should hide results', () => { - wrapper.setState({ showResults: true }); - wrapper.instance().hideResults(); - expect(wrapper.state().showResults).toBe(false); - wrapper.instance().hideResults(); - }); - - it('should handle focus', () => { - wrapper.setState({ searchQuery: 'hi', showResults: false }); - wrapper.instance().handleFocus(); - expect(wrapper.state().showResults).toBe(true); - - wrapper.setState({ searchQuery: '', showResults: false }); - wrapper.instance().handleFocus(); - expect(wrapper.state().showResults).toBe(false); - }); - - it('should handle mouseover', () => { - wrapper.setState({ selectedResult: undefined }); - wrapper.instance().handleMouseOverResult('selection'); - expect(wrapper.state().selectedResult).toBe('selection'); - }); - - it('should handle "enter" keyboard event', () => { - wrapper.setState({ selectedResult: undefined }); - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.Enter } })); - expect(router.push).not.toHaveBeenCalled(); - - wrapper.setState({ selectedResult: 'foo' }); - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.Enter } })); - - expect(router.push).toHaveBeenCalledWith({ - hash: '#foo', - pathname: '/admin/settings', - search: queryToSearch({ category: 'foo category' }), - }); - }); - - it('should handle "down" keyboard event', () => { - wrapper.setState({ selectedResult: undefined }); - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.DownArrow } })); - expect(wrapper.state().selectedResult).toBeUndefined(); - - wrapper.setState({ selectedResult: 'foo' }); - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.DownArrow } })); - expect(wrapper.state().selectedResult).toBe('sonar.new_code_period'); - - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.DownArrow } })); - expect(wrapper.state().selectedResult).toBe('sonar.new_code_period'); - }); - - it('should handle "up" keyboard event', () => { - wrapper.setState({ selectedResult: undefined }); - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.UpArrow } })); - expect(wrapper.state().selectedResult).toBeUndefined(); - - wrapper.setState({ selectedResult: 'sonar.new_code_period' }); - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.UpArrow } })); - expect(wrapper.state().selectedResult).toBe('foo'); - - wrapper.instance().handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.UpArrow } })); - expect(wrapper.state().selectedResult).toBe('foo'); - }); -}); - -describe('project settings search', () => { - it('should load the correct definitions', () => { - const wrapper = shallowRender({ component: mockComponent(), definitions: [] }); - - expect(Object.keys(wrapper.instance().definitionsByKey)).toHaveLength(1); - }); -}); - -function shallowRender(overrides: Partial<SettingsSearch['props']> = {}) { - return shallow<SettingsSearch>( - <SettingsSearch definitions={[mockDefinition()]} router={mockRouter()} {...overrides} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearchRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearchRenderer-test.tsx deleted file mode 100644 index 184dea62b53..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearchRenderer-test.tsx +++ /dev/null @@ -1,86 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockDefinition } from '../../../../helpers/mocks/settings'; -import { scrollToElement } from '../../../../helpers/scrolling'; -import SettingsSearchRenderer, { SettingsSearchRendererProps } from '../SettingsSearchRenderer'; - -jest.mock('../../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -jest.mock('react', () => { - return { - ...jest.requireActual('react'), - useRef: jest.fn(), - useEffect: jest.fn(), - }; -}); - -afterAll(() => { - jest.clearAllMocks(); -}); - -it('should render correctly when closed', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should render correctly when open', () => { - expect(shallowRender({ showResults: true })).toMatchSnapshot('no results'); - expect( - shallowRender({ - results: [mockDefinition({ name: 'Foo!' }), mockDefinition({ key: 'bar' })], - selectedResult: 'bar', - showResults: true, - }) - ).toMatchSnapshot('results'); -}); - -it('should scroll to selected element', () => { - const scrollable = {}; - const scrollableRef = { current: scrollable }; - const selected = {}; - const selectedRef = { current: selected }; - - (React.useRef as jest.Mock) - .mockImplementationOnce(() => scrollableRef) - .mockImplementationOnce(() => selectedRef); - (React.useEffect as jest.Mock).mockImplementationOnce((f) => f()); - - shallowRender(); - - expect(scrollToElement).toHaveBeenCalled(); -}); - -function shallowRender(overrides: Partial<SettingsSearchRendererProps> = {}) { - return shallow<SettingsSearchRendererProps>( - <SettingsSearchRenderer - searchQuery="" - showResults={false} - onClickOutside={jest.fn()} - onMouseOverResult={jest.fn()} - onSearchInputChange={jest.fn()} - onSearchInputFocus={jest.fn()} - onSearchInputKeyDown={jest.fn()} - {...overrides} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx deleted file mode 100644 index f71fec69b3a..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx +++ /dev/null @@ -1,75 +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 { mount, shallow } from 'enzyme'; -import * as React from 'react'; -import { mockSettingWithCategory } from '../../../../helpers/mocks/settings'; -import { mockLocation } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { - SubCategoryDefinitionsList, - SubCategoryDefinitionsListProps, -} from '../SubCategoryDefinitionsList'; - -jest.mock('../../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(''); - expect(shallowRender({ subCategory: 'qg' })).toMatchSnapshot('subcategory'); -}); - -it('should scroll if hash is defined and updated', async () => { - window.HTMLElement.prototype.scrollIntoView = jest.fn(); - const wrapper = shallowRender({ location: mockLocation({ hash: '#qg' }) }); - - await waitAndUpdate(wrapper); - - wrapper.find('h2').forEach((node) => mount(node.getElement())); - - expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalled(); - - wrapper.setProps({ location: mockLocation({ hash: '#email' }) }); - - expect(window.HTMLElement.prototype.scrollIntoView).toHaveBeenCalled(); -}); - -function shallowRender(props: Partial<SubCategoryDefinitionsListProps> = {}) { - return shallow<SubCategoryDefinitionsListProps>( - <SubCategoryDefinitionsList - category="general" - location={mockLocation()} - settings={[ - mockSettingWithCategory(), - mockSettingWithCategory({ - definition: { - key: 'qg', - category: 'general', - subCategory: 'qg', - fields: [], - options: [], - description: 'awesome description', - }, - }), - ]} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap deleted file mode 100644 index 1458c61e3d4..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap +++ /dev/null @@ -1,150 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render additional categories component correctly 1`] = ` -<withRouter(Languages) - categories={[]} - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } - definitions={[]} - selectedCategory="TEST" -/> -`; - -exports[`should render additional categories component correctly 2`] = `<NewCodePeriod />`; - -exports[`should render additional categories component correctly 3`] = ` -<AnalysisScope - categories={[]} - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } - definitions={[]} - selectedCategory="TEST" -/> -`; - -exports[`should render additional categories component correctly 4`] = ` -<withRouter(withAvailableFeaturesContext(AlmIntegration)) - categories={[]} - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } - definitions={[]} - selectedCategory="TEST" -/> -`; - -exports[`should render additional categories component correctly 5`] = ` -<withCurrentUserContext(PRDecorationBinding) - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } -/> -`; - -exports[`should render additional categories component correctly 6`] = ` -<withAvailableFeaturesContext(Authentication) - categories={[]} - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } - definitions={[]} - selectedCategory="TEST" -/> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap deleted file mode 100644 index 1a40aa6ea1d..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<Fragment> - <p - className="spacer-bottom" - > - settings.analysis_scope.wildcards.introduction - <withAppStateContext(DocLink) - className="spacer-left" - to="/project-administration/narrowing-the-focus/" - > - learn_more - </withAppStateContext(DocLink)> - </p> - <table - className="data spacer-bottom" - > - <tbody> - <tr> - <td> - * - </td> - <td> - settings.analysis_scope.wildcards.zero_more_char - </td> - </tr> - <tr> - <td> - ** - </td> - <td> - settings.analysis_scope.wildcards.zero_more_dir - </td> - </tr> - <tr> - <td> - ? - </td> - <td> - settings.analysis_scope.wildcards.single_char - </td> - </tr> - </tbody> - </table> - <div - className="settings-sub-category" - > - <CategoryDefinitionsList - category="TEST" - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } - definitions={[]} - /> - </div> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap deleted file mode 100644 index dddb190a866..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap +++ /dev/null @@ -1,143 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`disables save button on error 1`] = ` -<Fragment> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right button-success" - disabled={true} - onClick={[MockFunction]} - > - save - </Button> - <ResetButtonLink - className="spacer-right" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </div> -</Fragment> -`; - -exports[`displays cancel button when value changed and has error 1`] = ` -<Fragment> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right button-success" - disabled={true} - onClick={[MockFunction]} - > - save - </Button> - <ResetButtonLink - className="spacer-right" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </div> -</Fragment> -`; - -exports[`displays cancel button when value changed and no error 1`] = ` -<Fragment> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right button-success" - disabled={false} - onClick={[MockFunction]} - > - save - </Button> - <ResetButtonLink - className="spacer-right" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </div> -</Fragment> -`; - -exports[`displays default message when value is default 1`] = ` -<Fragment> - <div - className="spacer-top note" - style={ - { - "lineHeight": "24px", - } - } - > - settings._default - </div> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right" - onClick={[Function]} - > - reset_verb - </Button> - <span - className="note" - > - default - : - settings.default.no_value - </span> - </div> -</Fragment> -`; - -exports[`displays reset button when empty and not default 1`] = ` -<Fragment> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right" - onClick={[Function]} - > - reset_verb - </Button> - <span - className="note" - > - default - : - settings.default.no_value - </span> - </div> -</Fragment> -`; - -exports[`displays save button when it can be saved 1`] = ` -<Fragment> - <div - className="settings-definition-changes nowrap" - > - <Button - className="spacer-right button-success" - disabled={false} - onClick={[MockFunction]} - > - save - </Button> - <ResetButtonLink - className="spacer-right" - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </div> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionRenderer-test.tsx.snap deleted file mode 100644 index 021e807d1af..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionRenderer-test.tsx.snap +++ /dev/null @@ -1,571 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: changed value 1`] = ` -<div - className="settings-definition settings-definition-changed" - data-key="foo" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="property.foo.name" - > - property.foo.name - </h3> - <div - className="markdown small spacer-top" - dangerouslySetInnerHTML={ - { - "__html": "property.foo.description", - } - } - /> - <Tooltip - overlay="settings.key_x.foo" - > - <div - className="settings-definition-key note little-spacer-top" - > - settings.key_x.foo - </div> - </Tooltip> - </div> - <div - className="settings-definition-right" - > - <div - className="settings-definition-state" - /> - <form - onSubmit={[Function]} - > - <Input - hasValueChanged={true} - isEditing={false} - onCancel={[MockFunction]} - onChange={[MockFunction]} - onEditing={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - value="new value" - /> - <DefinitionActions - changedValue="new value" - hasError={false} - hasValueChanged={true} - isDefault={false} - isEditing={false} - onCancel={[MockFunction]} - onReset={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - </form> - </div> -</div> -`; - -exports[`should render correctly: in error 1`] = ` -<div - className="settings-definition" - data-key="foo" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="property.foo.name" - > - property.foo.name - </h3> - <div - className="markdown small spacer-top" - dangerouslySetInnerHTML={ - { - "__html": "property.foo.description", - } - } - /> - <Tooltip - overlay="settings.key_x.foo" - > - <div - className="settings-definition-key note little-spacer-top" - > - settings.key_x.foo - </div> - </Tooltip> - </div> - <div - className="settings-definition-right" - > - <div - className="settings-definition-state" - > - <span - className="text-danger" - > - <AlertErrorIcon - className="spacer-right" - /> - <span> - settings.state.validation_failed.validation message - </span> - </span> - </div> - <form - onSubmit={[Function]} - > - <Input - hasValueChanged={false} - isEditing={false} - onCancel={[MockFunction]} - onChange={[MockFunction]} - onEditing={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - <DefinitionActions - hasError={true} - hasValueChanged={false} - isDefault={false} - isEditing={false} - onCancel={[MockFunction]} - onReset={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - </form> - </div> -</div> -`; - -exports[`should render correctly: loading 1`] = ` -<div - className="settings-definition" - data-key="foo" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="property.foo.name" - > - property.foo.name - </h3> - <div - className="markdown small spacer-top" - dangerouslySetInnerHTML={ - { - "__html": "property.foo.description", - } - } - /> - <Tooltip - overlay="settings.key_x.foo" - > - <div - className="settings-definition-key note little-spacer-top" - > - settings.key_x.foo - </div> - </Tooltip> - </div> - <div - className="settings-definition-right" - > - <div - className="settings-definition-state" - > - <span - className="text-info" - > - <i - className="spinner spacer-right" - /> - settings.state.saving - </span> - </div> - <form - onSubmit={[Function]} - > - <Input - hasValueChanged={false} - isEditing={false} - onCancel={[MockFunction]} - onChange={[MockFunction]} - onEditing={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - <DefinitionActions - hasError={false} - hasValueChanged={false} - isDefault={false} - isEditing={false} - onCancel={[MockFunction]} - onReset={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - </form> - </div> -</div> -`; - -exports[`should render correctly: original value 1`] = ` -<div - className="settings-definition" - data-key="foo" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="property.foo.name" - > - property.foo.name - </h3> - <div - className="markdown small spacer-top" - dangerouslySetInnerHTML={ - { - "__html": "property.foo.description", - } - } - /> - <Tooltip - overlay="settings.key_x.foo" - > - <div - className="settings-definition-key note little-spacer-top" - > - settings.key_x.foo - </div> - </Tooltip> - </div> - <div - className="settings-definition-right" - > - <div - className="settings-definition-state" - /> - <form - onSubmit={[Function]} - > - <Input - hasValueChanged={false} - isEditing={false} - onCancel={[MockFunction]} - onChange={[MockFunction]} - onEditing={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": true, - "key": "foo", - "value": "original value", - } - } - value="original value" - /> - <DefinitionActions - hasError={false} - hasValueChanged={false} - isDefault={false} - isEditing={false} - onCancel={[MockFunction]} - onReset={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": true, - "key": "foo", - "value": "original value", - } - } - /> - </form> - </div> -</div> -`; - -exports[`should render correctly: success 1`] = ` -<div - className="settings-definition" - data-key="foo" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="property.foo.name" - > - property.foo.name - </h3> - <div - className="markdown small spacer-top" - dangerouslySetInnerHTML={ - { - "__html": "property.foo.description", - } - } - /> - <Tooltip - overlay="settings.key_x.foo" - > - <div - className="settings-definition-key note little-spacer-top" - > - settings.key_x.foo - </div> - </Tooltip> - </div> - <div - className="settings-definition-right" - > - <div - className="settings-definition-state" - > - <span - className="text-success" - > - <AlertSuccessIcon - className="spacer-right" - /> - settings.state.saved - </span> - </div> - <form - onSubmit={[Function]} - > - <Input - hasValueChanged={false} - isEditing={false} - onCancel={[MockFunction]} - onChange={[MockFunction]} - onEditing={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - <DefinitionActions - hasError={false} - hasValueChanged={false} - isDefault={false} - isEditing={false} - onCancel={[MockFunction]} - onReset={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - </form> - </div> -</div> -`; - -exports[`should render correctly: with description 1`] = ` -<div - className="settings-definition" - data-key="foo" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="property.foo.name" - > - property.foo.name - </h3> - <div - className="markdown small spacer-top" - dangerouslySetInnerHTML={ - { - "__html": "property.foo.description", - } - } - /> - <Tooltip - overlay="settings.key_x.foo" - > - <div - className="settings-definition-key note little-spacer-top" - > - settings.key_x.foo - </div> - </Tooltip> - </div> - <div - className="settings-definition-right" - > - <div - className="settings-definition-state" - /> - <form - onSubmit={[Function]} - > - <Input - hasValueChanged={false} - isEditing={false} - onCancel={[MockFunction]} - onChange={[MockFunction]} - onEditing={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "description": "description", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - <DefinitionActions - hasError={false} - hasValueChanged={false} - isDefault={false} - isEditing={false} - onCancel={[MockFunction]} - onReset={[MockFunction]} - onSave={[MockFunction]} - setting={ - { - "definition": { - "category": "foo category", - "description": "description", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - "hasValue": false, - "key": "foo", - } - } - /> - </form> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap deleted file mode 100644 index a27318bce98..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap +++ /dev/null @@ -1,358 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: default 1`] = ` -<div - className="settings-definition" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - > - email_configuration.test.title - </h3> - </div> - <form - className="settings-definition-right" - onSubmit={[Function]} - > - <MandatoryFieldsExplanation - className="form-field" - /> - <div - className="form-field" - > - <label - htmlFor="test-email-to" - > - email_configuration.test.to_address - <MandatoryFieldMarker /> - </label> - <input - className="settings-large-input" - disabled={false} - id="test-email-to" - onChange={[Function]} - required={true} - type="email" - value="luke@skywalker.biz" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-subject" - > - email_configuration.test.subject - </label> - <input - className="settings-large-input" - disabled={false} - id="test-email-subject" - onChange={[Function]} - type="text" - value="email_configuration.test.subject" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-message" - > - email_configuration.test.message - <MandatoryFieldMarker /> - </label> - <textarea - className="settings-large-input" - disabled={false} - id="test-email-message" - onChange={[Function]} - required={true} - rows={5} - value="email_configuration.test.message_text" - /> - </div> - <SubmitButton - disabled={false} - > - email_configuration.test.send - </SubmitButton> - </form> -</div> -`; - -exports[`should render correctly: error 1`] = ` -<div - className="settings-definition" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - > - email_configuration.test.title - </h3> - </div> - <form - className="settings-definition-right" - onSubmit={[Function]} - > - <div - className="form-field" - > - <Alert - variant="error" - > - Some error message - </Alert> - </div> - <MandatoryFieldsExplanation - className="form-field" - /> - <div - className="form-field" - > - <label - htmlFor="test-email-to" - > - email_configuration.test.to_address - <MandatoryFieldMarker /> - </label> - <input - className="settings-large-input" - disabled={false} - id="test-email-to" - onChange={[Function]} - required={true} - type="email" - value="luke@skywalker.biz" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-subject" - > - email_configuration.test.subject - </label> - <input - className="settings-large-input" - disabled={false} - id="test-email-subject" - onChange={[Function]} - type="text" - value="email_configuration.test.subject" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-message" - > - email_configuration.test.message - <MandatoryFieldMarker /> - </label> - <textarea - className="settings-large-input" - disabled={false} - id="test-email-message" - onChange={[Function]} - required={true} - rows={5} - value="email_configuration.test.message_text" - /> - </div> - <SubmitButton - disabled={false} - > - email_configuration.test.send - </SubmitButton> - </form> -</div> -`; - -exports[`should render correctly: sending 1`] = ` -<div - className="settings-definition" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - > - email_configuration.test.title - </h3> - </div> - <form - className="settings-definition-right" - onSubmit={[Function]} - > - <MandatoryFieldsExplanation - className="form-field" - /> - <div - className="form-field" - > - <label - htmlFor="test-email-to" - > - email_configuration.test.to_address - <MandatoryFieldMarker /> - </label> - <input - className="settings-large-input" - disabled={true} - id="test-email-to" - onChange={[Function]} - required={true} - type="email" - value="luke@skywalker.biz" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-subject" - > - email_configuration.test.subject - </label> - <input - className="settings-large-input" - disabled={true} - id="test-email-subject" - onChange={[Function]} - type="text" - value="email_configuration.test.subject" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-message" - > - email_configuration.test.message - <MandatoryFieldMarker /> - </label> - <textarea - className="settings-large-input" - disabled={true} - id="test-email-message" - onChange={[Function]} - required={true} - rows={5} - value="email_configuration.test.message_text" - /> - </div> - <SubmitButton - disabled={true} - > - email_configuration.test.send - </SubmitButton> - <DeferredSpinner - className="spacer-left" - /> - </form> -</div> -`; - -exports[`should render correctly: success 1`] = ` -<div - className="settings-definition" -> - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - > - email_configuration.test.title - </h3> - </div> - <form - className="settings-definition-right" - onSubmit={[Function]} - > - <div - className="form-field" - > - <Alert - variant="success" - > - email_configuration.test.email_was_sent_to_x.email@example.com - </Alert> - </div> - <MandatoryFieldsExplanation - className="form-field" - /> - <div - className="form-field" - > - <label - htmlFor="test-email-to" - > - email_configuration.test.to_address - <MandatoryFieldMarker /> - </label> - <input - className="settings-large-input" - disabled={false} - id="test-email-to" - onChange={[Function]} - required={true} - type="email" - value="luke@skywalker.biz" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-subject" - > - email_configuration.test.subject - </label> - <input - className="settings-large-input" - disabled={false} - id="test-email-subject" - onChange={[Function]} - type="text" - value="email_configuration.test.subject" - /> - </div> - <div - className="form-field" - > - <label - htmlFor="test-email-message" - > - email_configuration.test.message - <MandatoryFieldMarker /> - </label> - <textarea - className="settings-large-input" - disabled={false} - id="test-email-message" - onChange={[Function]} - required={true} - rows={5} - value="email_configuration.test.message_text" - /> - </div> - <SubmitButton - disabled={false} - > - email_configuration.test.send - </SubmitButton> - </form> -</div> -`; 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 deleted file mode 100644 index 30014d31064..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap +++ /dev/null @@ -1,96 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -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" - > - <Select - aria-labelledby="languages-category-title" - className="input-large select-settings-language" - onChange={[Function]} - options={ - [ - { - "label": "property.category.Java", - "originalValue": "Java", - "value": "java", - }, - { - "label": "property.category.JavaScript", - "originalValue": "JavaScript", - "value": "javascript", - }, - { - "label": "property.category.COBOL", - "originalValue": "COBOL", - "value": "cobol", - }, - ] - } - placeholder="settings.languages.select_a_language_placeholder" - value={ - { - "label": "property.category.Java", - "originalValue": "Java", - "value": "java", - } - } - /> - </div> - <div - className="settings-sub-category" - > - <CategoryDefinitionsList - category="java" - definitions={[]} - /> - </div> -</Fragment> -`; - -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" - > - <Select - aria-labelledby="languages-category-title" - className="input-large select-settings-language" - onChange={[Function]} - options={ - [ - { - "label": "property.category.Java", - "originalValue": "Java", - "value": "java", - }, - { - "label": "property.category.JavaScript", - "originalValue": "JavaScript", - "value": "javascript", - }, - { - "label": "property.category.COBOL", - "originalValue": "COBOL", - "value": "cobol", - }, - ] - } - placeholder="settings.languages.select_a_language_placeholder" - /> - </div> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-it.tsx.snap index 24a811bedac..24a811bedac 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-it.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap deleted file mode 100644 index 0ceb31d6227..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap +++ /dev/null @@ -1,83 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: for project 1`] = ` -<header - className="top-bar-outer" -> - <div - className="top-bar" - > - <div - className="top-bar-inner bordered-bottom big-padded-top padded-bottom" - > - <h1 - className="page-title" - > - project_settings.page - </h1> - <div - className="page-description spacer-top" - > - project_settings.page.description - </div> - <withRouter(SettingsSearch) - className="big-spacer-top" - component={ - { - "breadcrumbs": [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": [ - { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": [], - } - } - definitions={[]} - /> - </div> - </div> -</header> -`; - -exports[`should render correctly: global 1`] = ` -<header - className="top-bar-outer" -> - <div - className="top-bar" - > - <div - className="top-bar-inner bordered-bottom big-padded-top padded-bottom" - > - <h1 - className="page-title" - > - settings.page - </h1> - <div - className="page-description spacer-top" - > - <InstanceMessage - message="settings.page.description" - /> - </div> - <withRouter(SettingsSearch) - className="big-spacer-top" - definitions={[]} - /> - </div> - </div> -</header> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap deleted file mode 100644 index 0f002e7be11..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render default view correctly 1`] = ` -<withRouter(SettingsAppRenderer) - definitions={[]} - loading={false} -/> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap deleted file mode 100644 index 491942307b9..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap +++ /dev/null @@ -1,503 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render almintegration correctly 1`] = ` -<main - id="settings-page" -> - <Suggestions - suggestions="settings" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="settings.page" - /> - <PageHeader - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - <div - className="layout-page" - > - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="big-padded" - key="almintegration" - > - <withRouter(withAvailableFeaturesContext(AlmIntegration)) - categories={ - [ - "foo category", - "general", - ] - } - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - selectedCategory="almintegration" - /> - </div> - </div> - </div> - </div> -</main> -`; - -exports[`should render default view correctly 1`] = ` -<main - id="settings-page" -> - <Suggestions - suggestions="settings" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="settings.page" - /> - <PageHeader - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - <div - className="layout-page" - > - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="big-padded" - key="general" - > - <CategoryDefinitionsList - category="general" - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - </div> - </div> - </div> - </div> -</main> -`; - -exports[`should render default view correctly: All Categories List 1`] = ` -<div - className="layout-page-side-outer" -> - <div - className="layout-page-side" - style={ - { - "top": 0, - } - } - > - <div - className="layout-page-side-inner" - > - <withAvailableFeaturesContext(CategoriesList) - categories={ - [ - "foo category", - "general", - ] - } - defaultCategory="general" - selectedCategory="general" - /> - </div> - </div> -</div> -`; - -exports[`should render exclusions correctly 1`] = ` -<main - id="settings-page" -> - <Suggestions - suggestions="settings" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="settings.page" - /> - <PageHeader - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - <div - className="layout-page" - > - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="big-padded" - key="exclusions" - > - <AnalysisScope - categories={ - [ - "foo category", - "general", - ] - } - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - selectedCategory="exclusions" - /> - </div> - </div> - </div> - </div> -</main> -`; - -exports[`should render languages correctly 1`] = ` -<main - id="settings-page" -> - <Suggestions - suggestions="settings" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="settings.page" - /> - <PageHeader - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - <div - className="layout-page" - > - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="big-padded" - key="languages" - > - <withRouter(Languages) - categories={ - [ - "foo category", - "general", - ] - } - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - selectedCategory="languages" - /> - </div> - </div> - </div> - </div> -</main> -`; - -exports[`should render new_code_period correctly 1`] = ` -<main - id="settings-page" -> - <Suggestions - suggestions="settings" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="settings.page" - /> - <PageHeader - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - <div - className="layout-page" - > - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="big-padded" - key="new_code_period" - > - <NewCodePeriod /> - </div> - </div> - </div> - </div> -</main> -`; - -exports[`should render pull_request_decoration_binding correctly 1`] = ` -<main - id="settings-page" -> - <Suggestions - suggestions="settings" - /> - <Helmet - defer={false} - encodeSpecialCharacters={true} - prioritizeSeoTags={false} - title="settings.page" - /> - <PageHeader - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - <div - className="layout-page" - > - <ScreenPositionHelper - className="layout-page-side-outer" - > - <Component /> - </ScreenPositionHelper> - <div - className="layout-page-main" - > - <div - className="layout-page-main-inner" - > - <div - className="big-padded" - key="pull_request_decoration_binding" - > - <CategoryDefinitionsList - category="pull_request_decoration_binding" - definitions={ - [ - { - "category": "foo category", - "fields": [], - "key": "foo", - "options": [], - "subCategory": "foo subCat", - }, - { - "category": "general", - "fields": [], - "key": "bar", - "options": [], - "subCategory": "foo subCat", - }, - ] - } - /> - </div> - </div> - </div> - </div> -</main> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap deleted file mode 100644 index a50c55c2c6a..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap +++ /dev/null @@ -1,134 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly when closed 1`] = ` -<OutsideClickHandler - onClickOutside={[MockFunction]} -> - <div - className="dropdown" - > - <SearchBox - onChange={[MockFunction]} - onFocus={[MockFunction]} - onKeyDown={[MockFunction]} - placeholder="settings.search.placeholder" - value="" - /> - </div> -</OutsideClickHandler> -`; - -exports[`should render correctly when open: no results 1`] = ` -<OutsideClickHandler - onClickOutside={[MockFunction]} -> - <div - className="dropdown" - > - <SearchBox - onChange={[MockFunction]} - onFocus={[MockFunction]} - onKeyDown={[MockFunction]} - placeholder="settings.search.placeholder" - value="" - /> - <DropdownOverlay - noPadding={true} - > - <ul - className="settings-search-results menu" - > - <div - className="big-padded" - > - no_results - </div> - </ul> - </DropdownOverlay> - </div> -</OutsideClickHandler> -`; - -exports[`should render correctly when open: results 1`] = ` -<OutsideClickHandler - onClickOutside={[MockFunction]} -> - <div - className="dropdown" - > - <SearchBox - onChange={[MockFunction]} - onFocus={[MockFunction]} - onKeyDown={[MockFunction]} - placeholder="settings.search.placeholder" - value="" - /> - <DropdownOverlay - noPadding={true} - > - <ul - className="settings-search-results menu" - > - <li - className="spacer-bottom spacer-top" - key="foo" - > - <ForwardRef(Link) - onClick={[MockFunction]} - onMouseEnter={[Function]} - to={ - { - "hash": "#foo", - "pathname": "/admin/settings", - "search": "?category=foo+category", - } - } - > - <div - className="settings-search-result-title display-flex-space-between" - > - <h3> - Foo! - </h3> - </div> - <div - className="note spacer-top" - > - settings.key_x.foo - </div> - </ForwardRef(Link)> - </li> - <li - className="spacer-bottom spacer-top active" - key="bar" - > - <ForwardRef(Link) - onClick={[MockFunction]} - onMouseEnter={[Function]} - to={ - { - "hash": "#bar", - "pathname": "/admin/settings", - "search": "?category=foo+category", - } - } - > - <div - className="settings-search-result-title display-flex-space-between" - > - <h3> - foo subCat - </h3> - </div> - <div - className="note spacer-top" - > - settings.key_x.bar - </div> - </ForwardRef(Link)> - </li> - </ul> - </DropdownOverlay> - </div> -</OutsideClickHandler> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap deleted file mode 100644 index 9e70241ec02..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap +++ /dev/null @@ -1,135 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<ul - className="settings-sub-categories-list" -> - <li - key="email" - > - <h2 - className="settings-sub-category-name" - data-key="email" - > - property.category.general.email - </h2> - <div - className="settings-sub-category-description markdown" - dangerouslySetInnerHTML={ - { - "__html": "property.category.general.email.description", - } - } - /> - <DefinitionsList - scrollToDefinition={[Function]} - settings={ - [ - { - "definition": { - "category": "general", - "description": "When Foo then Bar", - "fields": [], - "key": "foo", - "name": "Foo setting", - "options": [], - "subCategory": "email", - "type": "INTEGER", - }, - "hasValue": true, - "inherited": true, - "key": "foo", - "value": "42", - }, - ] - } - /> - <withCurrentUserContext(EmailForm) /> - </li> - <li - key="qg" - > - <h2 - className="settings-sub-category-name" - data-key="qg" - > - property.category.general.qg - </h2> - <div - className="settings-sub-category-description markdown" - dangerouslySetInnerHTML={ - { - "__html": "property.category.general.qg.description", - } - } - /> - <DefinitionsList - scrollToDefinition={[Function]} - settings={ - [ - { - "definition": { - "category": "general", - "description": "awesome description", - "fields": [], - "key": "qg", - "options": [], - "subCategory": "qg", - }, - "hasValue": true, - "inherited": true, - "key": "foo", - "value": "42", - }, - ] - } - /> - </li> -</ul> -`; - -exports[`should render correctly: subcategory 1`] = ` -<ul - className="settings-sub-categories-list" -> - <li - key="qg" - > - <h2 - className="settings-sub-category-name" - data-key="qg" - > - property.category.general.qg - </h2> - <div - className="settings-sub-category-description markdown" - dangerouslySetInnerHTML={ - { - "__html": "property.category.general.qg.description", - } - } - /> - <DefinitionsList - scrollToDefinition={[Function]} - settings={ - [ - { - "definition": { - "category": "general", - "description": "awesome description", - "fields": [], - "key": "qg", - "options": [], - "subCategory": "qg", - }, - "hasValue": true, - "inherited": true, - "key": "foo", - "value": "42", - }, - ] - } - /> - </li> -</ul> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx index 9524807ca5b..80cb2eb7fd5 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx @@ -19,7 +19,8 @@ */ import * as React from 'react'; import { DeleteButton } from '../../../../components/controls/buttons'; -import { DefaultSpecializedInputProps, getEmptyValue } from '../../utils'; +import { translateWithParameters } from '../../../../helpers/l10n'; +import { DefaultSpecializedInputProps, getEmptyValue, getPropertyName } from '../../utils'; import PrimitiveInput from './PrimitiveInput'; export default class MultiValueInput extends React.PureComponent<DefaultSpecializedInputProps> { @@ -56,6 +57,11 @@ export default class MultiValueInput extends React.PureComponent<DefaultSpeciali <div className="display-inline-block spacer-left"> <DeleteButton className="js-remove-value" + aria-label={translateWithParameters( + 'settings.definition.delete_value', + getPropertyName(setting.definition), + value + )} onClick={() => this.handleDeleteValue(index)} /> </div> diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx index 098f988ea1c..4ca4a1526b7 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx @@ -20,7 +20,7 @@ import classNames from 'classnames'; import * as React from 'react'; import { KeyboardKeys } from '../../../../helpers/keycodes'; -import { DefaultSpecializedInputProps } from '../../utils'; +import { DefaultSpecializedInputProps, getPropertyName } from '../../utils'; export interface SimpleInputProps extends DefaultSpecializedInputProps { value: string | number; @@ -40,7 +40,7 @@ export default class SimpleInput extends React.PureComponent<SimpleInputProps> { }; render() { - const { autoComplete, autoFocus, className, name, value = '', type } = this.props; + const { autoComplete, autoFocus, className, name, value = '', setting, type } = this.props; return ( <input autoComplete={autoComplete} @@ -51,6 +51,7 @@ export default class SimpleInput extends React.PureComponent<SimpleInputProps> { onKeyDown={this.handleKeyDown} type={type} value={value} + aria-label={getPropertyName(setting.definition)} /> ); } diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css index ad2843792e4..ecf27c1be01 100644 --- a/server/sonar-web/src/main/js/apps/settings/styles.css +++ b/server/sonar-web/src/main/js/apps/settings/styles.css @@ -200,6 +200,16 @@ overflow-x: hidden; } +.settings-search-results > li > a:hover { + background-color: unset; + border-left-color: unset; +} + +.settings-search-results > li.active > a { + background-color: var(--neutral50); + border-left-color: var(--blacka60); +} + .fixed-footer { position: sticky; bottom: 0px; diff --git a/server/sonar-web/src/main/js/apps/settings/utils.ts b/server/sonar-web/src/main/js/apps/settings/utils.ts index d1654f6e323..514e50acdad 100644 --- a/server/sonar-web/src/main/js/apps/settings/utils.ts +++ b/server/sonar-web/src/main/js/apps/settings/utils.ts @@ -56,7 +56,11 @@ export interface DefaultInputProps { export function getPropertyName(definition: SettingDefinition) { const key = `property.${definition.key}.name`; - return hasMessage(key) ? translate(key) : definition.name; + if (hasMessage(key)) { + return translate(key); + } + + return definition.name ?? definition.key; } export function getPropertyDescription(definition: SettingDefinition) { |