]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18580 add RTL for settings (part1)
authorstanislavh <stanislav.honcharov@sonarsource.com>
Wed, 1 Mar 2023 12:32:28 +0000 (13:32 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 2 Mar 2023 20:03:50 +0000 (20:03 +0000)
46 files changed:
server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts
server/sonar-web/src/main/js/apps/settings/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
server/sonar-web/src/main/js/apps/settings/components/DefinitionActions.tsx
server/sonar-web/src/main/js/apps/settings/components/SettingsApp.tsx
server/sonar-web/src/main/js/apps/settings/components/SettingsAppRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/SettingsSearchRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/CategoryDefinitionsList-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionActions-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/DefinitionRenderer-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/EmailForm-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/Languages-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/PageHeader-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsApp-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsAppRenderer-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearch-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/SettingsSearchRenderer-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/SubCategoryDefinitionsList-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AnalysisScope-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionActions-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/DefinitionRenderer-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/EmailForm-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/Languages-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-it.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/PageHeader-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsApp-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsAppRenderer-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SettingsSearchRenderer-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/SubCategoryDefinitionsList-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/SimpleInput.tsx
server/sonar-web/src/main/js/apps/settings/styles.css
server/sonar-web/src/main/js/apps/settings/utils.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index ac36f1448f1db168cdb3b26b1516b854b4eb2a82..abfd964dcb8df01fb493bf7ad170b7ae502080b2 100644 (file)
  */
 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);
+}
index fde31d3de76c001f9b5c8ea404514836b39e22c8..c3643ff8af269273170d5477e08ccdc2b858dace 100644 (file)
@@ -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 = {
index a9e7f6f89f7812028aae7b99646aab38efe1cbdb..a039b5976263dd485d16e41f06927d7d59786588 100644 (file)
@@ -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
index 22130b7493d70f4bcac340afbda7e8e436ffadae..f90f0072f7c500e57cccce01fb66c7b04a35c341 100644 (file)
@@ -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>
           )}
index f9d2306f11eff52cbe7110e83661cad6ceae403a..e05ce398856b9b7411bd008dd034a2296f18f05d 100644 (file)
@@ -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 };
 
index 9d844cc2133fd62f991cb2319a32751c0cfd40d6..7bc291da1a8e6706f8168ab8f7648c33f3a3fa2e 100644 (file)
@@ -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,
index f79861d91bf78b876bab723b2efaf329bf858c13..c1cfa10fca947972fd7adec76f7000adb5c8b1e0 100644 (file)
@@ -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
index b904584590f58bde581fbbb28ed14e82cb352b4b..697791cc20d26b4cd82a9367e25882a3171e5936 100644 (file)
@@ -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 (file)
index a9977f2..0000000
+++ /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();
-});
index e66cafe8d499080265a5fdb069551fceed8f67de..f860ebab0ab75180fafae3ad9aecf076d6974674 100644 (file)
@@ -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 (file)
index c7ad9c6..0000000
+++ /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 (file)
index 926c874..0000000
+++ /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-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx
new file mode 100644 (file)
index 0000000..40869c5
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * 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 { getValue, resetSettingValue, setSettingValue } from '../../../../api/settings';
+import { mockDefinition, mockSettingValue } from '../../../../helpers/mocks/settings';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { SettingType } from '../../../../types/settings';
+import Definition from '../Definition';
+
+jest.mock('../../../../api/settings', () => ({
+  getValue: jest.fn().mockResolvedValue({}),
+  resetSettingValue: jest.fn().mockResolvedValue(undefined),
+  setSettingValue: jest.fn().mockResolvedValue(undefined),
+}));
+
+beforeAll(() => {
+  jest.useFakeTimers();
+});
+
+afterAll(() => {
+  jest.runOnlyPendingTimers();
+  jest.useRealTimers();
+});
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+describe('Handle change (and check)', () => {
+  it.each([
+    ['empty, no default', mockDefinition(), '', 'settings.state.value_cant_be_empty_no_default'],
+    [
+      'empty, default',
+      mockDefinition({ defaultValue: 'dflt' }),
+      '',
+      'settings.state.value_cant_be_empty',
+    ],
+    [
+      'invalid url',
+      mockDefinition({ key: 'sonar.core.serverBaseURL' }),
+      '%invalid',
+      'settings.state.url_not_valid.%invalid',
+    ],
+    [
+      'valid url',
+      mockDefinition({ key: 'sonar.core.serverBaseURL' }),
+      'http://www.sonarqube.org',
+      undefined,
+    ],
+    [
+      'invalid JSON',
+      mockDefinition({ type: SettingType.JSON }),
+      '{{broken: "json}',
+      'Unexpected token { in JSON at position 1',
+    ],
+    ['valid JSON', mockDefinition({ type: SettingType.JSON }), '{"validJson": true}', undefined],
+  ])(
+    'should handle change (and check value): %s',
+    (_caseName, definition, changedValue, expectedValidationMessage) => {
+      const wrapper = shallowRender({ definition });
+
+      wrapper.instance().handleChange(changedValue);
+
+      expect(wrapper.state().changedValue).toBe(changedValue);
+      expect(wrapper.state().success).toBe(false);
+      expect(wrapper.state().validationMessage).toBe(expectedValidationMessage);
+    }
+  );
+});
+
+it('should handle cancel', () => {
+  const wrapper = shallowRender();
+  wrapper.setState({ changedValue: 'whatever', validationMessage: 'something wrong' });
+
+  wrapper.instance().handleCancel();
+
+  expect(wrapper.state().changedValue).toBeUndefined();
+  expect(wrapper.state().validationMessage).toBeUndefined();
+});
+
+describe('handleSave', () => {
+  it('should ignore when value unchanged', () => {
+    const wrapper = shallowRender();
+
+    wrapper.instance().handleSave();
+
+    expect(wrapper.state().loading).toBe(false);
+    expect(setSettingValue).not.toHaveBeenCalled();
+  });
+
+  it('should handle an empty value', () => {
+    const wrapper = shallowRender();
+
+    wrapper.setState({ changedValue: '' });
+
+    wrapper.instance().handleSave();
+
+    expect(wrapper.state().loading).toBe(false);
+    expect(wrapper.state().validationMessage).toBe('settings.state.value_cant_be_empty');
+    expect(setSettingValue).not.toHaveBeenCalled();
+  });
+
+  it('should save and update setting value', async () => {
+    const settingValue = mockSettingValue();
+    (getValue as jest.Mock).mockResolvedValueOnce(settingValue);
+    const definition = mockDefinition();
+    const wrapper = shallowRender({ definition });
+
+    wrapper.setState({ changedValue: 'new value' });
+
+    wrapper.instance().handleSave();
+
+    expect(wrapper.state().loading).toBe(true);
+
+    await waitAndUpdate(wrapper);
+
+    expect(setSettingValue).toHaveBeenCalledWith(definition, 'new value', undefined);
+    expect(getValue).toHaveBeenCalledWith({ key: definition.key, component: undefined });
+    expect(wrapper.state().changedValue).toBeUndefined();
+    expect(wrapper.state().loading).toBe(false);
+    expect(wrapper.state().success).toBe(true);
+    expect(wrapper.state().settingValue).toBe(settingValue);
+
+    jest.runAllTimers();
+    expect(wrapper.state().success).toBe(false);
+  });
+});
+
+it('should reset and update setting value', async () => {
+  const settingValue = mockSettingValue();
+  (getValue as jest.Mock).mockResolvedValueOnce(settingValue);
+  const definition = mockDefinition();
+  const wrapper = shallowRender({ definition });
+
+  wrapper.instance().handleReset();
+
+  expect(wrapper.state().loading).toBe(true);
+
+  await waitAndUpdate(wrapper);
+
+  expect(resetSettingValue).toHaveBeenCalledWith({ keys: definition.key, component: undefined });
+  expect(getValue).toHaveBeenCalledWith({ key: definition.key, component: undefined });
+  expect(wrapper.state().changedValue).toBeUndefined();
+  expect(wrapper.state().loading).toBe(false);
+  expect(wrapper.state().success).toBe(true);
+  expect(wrapper.state().settingValue).toBe(settingValue);
+
+  jest.runAllTimers();
+  expect(wrapper.state().success).toBe(false);
+});
+
+function shallowRender(props: Partial<Definition['props']> = {}) {
+  return shallow<Definition>(<Definition definition={mockDefinition()} {...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-test.tsx
deleted file mode 100644 (file)
index 40869c5..0000000
+++ /dev/null
@@ -1,172 +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 { getValue, resetSettingValue, setSettingValue } from '../../../../api/settings';
-import { mockDefinition, mockSettingValue } from '../../../../helpers/mocks/settings';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import { SettingType } from '../../../../types/settings';
-import Definition from '../Definition';
-
-jest.mock('../../../../api/settings', () => ({
-  getValue: jest.fn().mockResolvedValue({}),
-  resetSettingValue: jest.fn().mockResolvedValue(undefined),
-  setSettingValue: jest.fn().mockResolvedValue(undefined),
-}));
-
-beforeAll(() => {
-  jest.useFakeTimers();
-});
-
-afterAll(() => {
-  jest.runOnlyPendingTimers();
-  jest.useRealTimers();
-});
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-describe('Handle change (and check)', () => {
-  it.each([
-    ['empty, no default', mockDefinition(), '', 'settings.state.value_cant_be_empty_no_default'],
-    [
-      'empty, default',
-      mockDefinition({ defaultValue: 'dflt' }),
-      '',
-      'settings.state.value_cant_be_empty',
-    ],
-    [
-      'invalid url',
-      mockDefinition({ key: 'sonar.core.serverBaseURL' }),
-      '%invalid',
-      'settings.state.url_not_valid.%invalid',
-    ],
-    [
-      'valid url',
-      mockDefinition({ key: 'sonar.core.serverBaseURL' }),
-      'http://www.sonarqube.org',
-      undefined,
-    ],
-    [
-      'invalid JSON',
-      mockDefinition({ type: SettingType.JSON }),
-      '{{broken: "json}',
-      'Unexpected token { in JSON at position 1',
-    ],
-    ['valid JSON', mockDefinition({ type: SettingType.JSON }), '{"validJson": true}', undefined],
-  ])(
-    'should handle change (and check value): %s',
-    (_caseName, definition, changedValue, expectedValidationMessage) => {
-      const wrapper = shallowRender({ definition });
-
-      wrapper.instance().handleChange(changedValue);
-
-      expect(wrapper.state().changedValue).toBe(changedValue);
-      expect(wrapper.state().success).toBe(false);
-      expect(wrapper.state().validationMessage).toBe(expectedValidationMessage);
-    }
-  );
-});
-
-it('should handle cancel', () => {
-  const wrapper = shallowRender();
-  wrapper.setState({ changedValue: 'whatever', validationMessage: 'something wrong' });
-
-  wrapper.instance().handleCancel();
-
-  expect(wrapper.state().changedValue).toBeUndefined();
-  expect(wrapper.state().validationMessage).toBeUndefined();
-});
-
-describe('handleSave', () => {
-  it('should ignore when value unchanged', () => {
-    const wrapper = shallowRender();
-
-    wrapper.instance().handleSave();
-
-    expect(wrapper.state().loading).toBe(false);
-    expect(setSettingValue).not.toHaveBeenCalled();
-  });
-
-  it('should handle an empty value', () => {
-    const wrapper = shallowRender();
-
-    wrapper.setState({ changedValue: '' });
-
-    wrapper.instance().handleSave();
-
-    expect(wrapper.state().loading).toBe(false);
-    expect(wrapper.state().validationMessage).toBe('settings.state.value_cant_be_empty');
-    expect(setSettingValue).not.toHaveBeenCalled();
-  });
-
-  it('should save and update setting value', async () => {
-    const settingValue = mockSettingValue();
-    (getValue as jest.Mock).mockResolvedValueOnce(settingValue);
-    const definition = mockDefinition();
-    const wrapper = shallowRender({ definition });
-
-    wrapper.setState({ changedValue: 'new value' });
-
-    wrapper.instance().handleSave();
-
-    expect(wrapper.state().loading).toBe(true);
-
-    await waitAndUpdate(wrapper);
-
-    expect(setSettingValue).toHaveBeenCalledWith(definition, 'new value', undefined);
-    expect(getValue).toHaveBeenCalledWith({ key: definition.key, component: undefined });
-    expect(wrapper.state().changedValue).toBeUndefined();
-    expect(wrapper.state().loading).toBe(false);
-    expect(wrapper.state().success).toBe(true);
-    expect(wrapper.state().settingValue).toBe(settingValue);
-
-    jest.runAllTimers();
-    expect(wrapper.state().success).toBe(false);
-  });
-});
-
-it('should reset and update setting value', async () => {
-  const settingValue = mockSettingValue();
-  (getValue as jest.Mock).mockResolvedValueOnce(settingValue);
-  const definition = mockDefinition();
-  const wrapper = shallowRender({ definition });
-
-  wrapper.instance().handleReset();
-
-  expect(wrapper.state().loading).toBe(true);
-
-  await waitAndUpdate(wrapper);
-
-  expect(resetSettingValue).toHaveBeenCalledWith({ keys: definition.key, component: undefined });
-  expect(getValue).toHaveBeenCalledWith({ key: definition.key, component: undefined });
-  expect(wrapper.state().changedValue).toBeUndefined();
-  expect(wrapper.state().loading).toBe(false);
-  expect(wrapper.state().success).toBe(true);
-  expect(wrapper.state().settingValue).toBe(settingValue);
-
-  jest.runAllTimers();
-  expect(wrapper.state().success).toBe(false);
-});
-
-function shallowRender(props: Partial<Definition['props']> = {}) {
-  return shallow<Definition>(<Definition definition={mockDefinition()} {...props} />);
-}
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 (file)
index d542b9a..0000000
+++ /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 (file)
index 0010f95..0000000
+++ /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 (file)
index c0ccbc0..0000000
+++ /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 (file)
index 0000000..9e0868e
--- /dev/null
@@ -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 (file)
index 2a54262..0000000
+++ /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-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx
new file mode 100644 (file)
index 0000000..c95f2e1
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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 { getNewCodePeriod, setNewCodePeriod } from '../../../../api/newCodePeriod';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import NewCodePeriod from '../NewCodePeriod';
+
+jest.mock('../../../../api/newCodePeriod', () => ({
+  getNewCodePeriod: jest.fn().mockResolvedValue({}),
+  setNewCodePeriod: jest.fn(() => Promise.resolve()),
+}));
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+it('should render correctly', () => {
+  expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should load the current new code period on mount', async () => {
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(getNewCodePeriod).toHaveBeenCalledTimes(1);
+  expect(wrapper.state('currentSetting')).toBe('PREVIOUS_VERSION');
+});
+
+it('should load the current new code period with value on mount', async () => {
+  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
+
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(getNewCodePeriod).toHaveBeenCalledTimes(1);
+  expect(wrapper.state('currentSetting')).toBe('NUMBER_OF_DAYS');
+  expect(wrapper.state('days')).toBe('42');
+});
+
+it('should only show the save button after changing the setting', async () => {
+  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'PREVIOUS_VERSION' });
+
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.state('selected')).toBe('PREVIOUS_VERSION');
+  expect(wrapper.find('SubmitButton')).toHaveLength(0);
+
+  wrapper.instance().onSelectSetting('NUMBER_OF_DAYS');
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.find('SubmitButton')).toHaveLength(1);
+});
+
+it('should disable the button if the days are invalid', async () => {
+  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
+
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+
+  wrapper.instance().onSelectDays('asd');
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.find('SubmitButton').first().prop('disabled')).toBe(true);
+
+  wrapper.instance().onSelectDays('23');
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.find('SubmitButton').first().prop('disabled')).toBe(false);
+});
+
+it('should submit correctly', async () => {
+  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
+
+  const preventDefault = jest.fn();
+
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  wrapper.instance().onSelectSetting('PREVIOUS_VERSION');
+  await waitAndUpdate(wrapper);
+
+  wrapper.find('form').simulate('submit', { preventDefault });
+
+  expect(preventDefault).toHaveBeenCalledTimes(1);
+  expect(setNewCodePeriod).toHaveBeenCalledWith({ type: 'PREVIOUS_VERSION', value: undefined });
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
+});
+
+it('should submit correctly with days', async () => {
+  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
+
+  const preventDefault = jest.fn();
+
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  wrapper.instance().onSelectDays('66');
+  await waitAndUpdate(wrapper);
+
+  wrapper.find('form').simulate('submit', { preventDefault });
+
+  expect(preventDefault).toHaveBeenCalledTimes(1);
+  expect(setNewCodePeriod).toHaveBeenCalledWith({ type: 'NUMBER_OF_DAYS', value: '66' });
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
+});
+
+function shallowRender() {
+  return shallow<NewCodePeriod>(<NewCodePeriod />);
+}
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-test.tsx
deleted file mode 100644 (file)
index c95f2e1..0000000
+++ /dev/null
@@ -1,126 +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 { getNewCodePeriod, setNewCodePeriod } from '../../../../api/newCodePeriod';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import NewCodePeriod from '../NewCodePeriod';
-
-jest.mock('../../../../api/newCodePeriod', () => ({
-  getNewCodePeriod: jest.fn().mockResolvedValue({}),
-  setNewCodePeriod: jest.fn(() => Promise.resolve()),
-}));
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should load the current new code period on mount', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(getNewCodePeriod).toHaveBeenCalledTimes(1);
-  expect(wrapper.state('currentSetting')).toBe('PREVIOUS_VERSION');
-});
-
-it('should load the current new code period with value on mount', async () => {
-  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
-
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(getNewCodePeriod).toHaveBeenCalledTimes(1);
-  expect(wrapper.state('currentSetting')).toBe('NUMBER_OF_DAYS');
-  expect(wrapper.state('days')).toBe('42');
-});
-
-it('should only show the save button after changing the setting', async () => {
-  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'PREVIOUS_VERSION' });
-
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.state('selected')).toBe('PREVIOUS_VERSION');
-  expect(wrapper.find('SubmitButton')).toHaveLength(0);
-
-  wrapper.instance().onSelectSetting('NUMBER_OF_DAYS');
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.find('SubmitButton')).toHaveLength(1);
-});
-
-it('should disable the button if the days are invalid', async () => {
-  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
-
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-
-  wrapper.instance().onSelectDays('asd');
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.find('SubmitButton').first().prop('disabled')).toBe(true);
-
-  wrapper.instance().onSelectDays('23');
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.find('SubmitButton').first().prop('disabled')).toBe(false);
-});
-
-it('should submit correctly', async () => {
-  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
-
-  const preventDefault = jest.fn();
-
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  wrapper.instance().onSelectSetting('PREVIOUS_VERSION');
-  await waitAndUpdate(wrapper);
-
-  wrapper.find('form').simulate('submit', { preventDefault });
-
-  expect(preventDefault).toHaveBeenCalledTimes(1);
-  expect(setNewCodePeriod).toHaveBeenCalledWith({ type: 'PREVIOUS_VERSION', value: undefined });
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
-});
-
-it('should submit correctly with days', async () => {
-  (getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });
-
-  const preventDefault = jest.fn();
-
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  wrapper.instance().onSelectDays('66');
-  await waitAndUpdate(wrapper);
-
-  wrapper.find('form').simulate('submit', { preventDefault });
-
-  expect(preventDefault).toHaveBeenCalledTimes(1);
-  expect(setNewCodePeriod).toHaveBeenCalledWith({ type: 'NUMBER_OF_DAYS', value: '66' });
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
-});
-
-function shallowRender() {
-  return shallow<NewCodePeriod>(<NewCodePeriod />);
-}
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 (file)
index 4184301..0000000
+++ /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 (file)
index 0000000..c4a7d25
--- /dev/null
@@ -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 (file)
index a4677e6..0000000
+++ /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 (file)
index e88fe23..0000000
+++ /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 (file)
index bc6705d..0000000
+++ /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 (file)
index 184dea6..0000000
+++ /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 (file)
index f71fec6..0000000
+++ /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 (file)
index 1458c61..0000000
+++ /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 (file)
index 1a40aa6..0000000
+++ /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 (file)
index dddb190..0000000
+++ /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 (file)
index 021e807..0000000
+++ /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 (file)
index a27318b..0000000
+++ /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 (file)
index 30014d3..0000000
+++ /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-it.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-it.tsx.snap
new file mode 100644 (file)
index 0000000..24a811b
--- /dev/null
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<ul
+  className="settings-sub-categories-list"
+>
+  <li>
+    <ul
+      className="settings-definitions-list"
+    >
+      <li>
+        <div
+          className="settings-definition"
+        >
+          <div
+            className="settings-definition-left"
+          >
+            <h3
+              className="settings-definition-name"
+              title="settings.new_code_period.title"
+            >
+              settings.new_code_period.title
+            </h3>
+            <div
+              className="small big-spacer-top"
+            >
+              <FormattedMessage
+                defaultMessage="settings.new_code_period.description"
+                id="settings.new_code_period.description"
+                values={
+                  {
+                    "link": <withAppStateContext(DocLink)
+                      to="/project-administration/defining-new-code/"
+                    >
+                      learn_more
+                    </withAppStateContext(DocLink)>,
+                  }
+                }
+              />
+              <p
+                className="spacer-top"
+              >
+                settings.new_code_period.description2
+              </p>
+            </div>
+          </div>
+          <div
+            className="settings-definition-right"
+          >
+            <DeferredSpinner />
+          </div>
+        </div>
+      </li>
+    </ul>
+  </li>
+</ul>
+`;
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-test.tsx.snap
deleted file mode 100644 (file)
index 24a811b..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<ul
-  className="settings-sub-categories-list"
->
-  <li>
-    <ul
-      className="settings-definitions-list"
-    >
-      <li>
-        <div
-          className="settings-definition"
-        >
-          <div
-            className="settings-definition-left"
-          >
-            <h3
-              className="settings-definition-name"
-              title="settings.new_code_period.title"
-            >
-              settings.new_code_period.title
-            </h3>
-            <div
-              className="small big-spacer-top"
-            >
-              <FormattedMessage
-                defaultMessage="settings.new_code_period.description"
-                id="settings.new_code_period.description"
-                values={
-                  {
-                    "link": <withAppStateContext(DocLink)
-                      to="/project-administration/defining-new-code/"
-                    >
-                      learn_more
-                    </withAppStateContext(DocLink)>,
-                  }
-                }
-              />
-              <p
-                className="spacer-top"
-              >
-                settings.new_code_period.description2
-              </p>
-            </div>
-          </div>
-          <div
-            className="settings-definition-right"
-          >
-            <DeferredSpinner />
-          </div>
-        </div>
-      </li>
-    </ul>
-  </li>
-</ul>
-`;
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 (file)
index 0ceb31d..0000000
+++ /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 (file)
index 0f002e7..0000000
+++ /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 (file)
index 4919423..0000000
+++ /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 (file)
index a50c55c..0000000
+++ /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 (file)
index 9e70241..0000000
+++ /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>
-`;
index 9524807ca5be2fba58b18a551abbdb1b2c49f2e0..80cb2eb7fd57722f6f63cd984a67348c7a71b120 100644 (file)
@@ -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>
index 098f988ea1c49e095672e2cfb6b0fc87e0c7b344..4ca4a1526b7519c1eff3ab1c6dfa33aaf2e06cb9 100644 (file)
@@ -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)}
       />
     );
   }
index ad2843792e4ca47128b3e2c33a0c92a7e866d972..ecf27c1be015da2a21ac83c62d4c916e9e2a3ce3 100644 (file)
   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;
index d1654f6e323474bc9e1c0e2a288e15b9344657bf..514e50acdadbecf075b65002dca5ca121ce02a21 100644 (file)
@@ -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) {
index 4e085db061aed6a97ce75960dd172a16ce3accd5..77c79ed4e4931dc42ea7272ef8d5ed308653b0cb 100644 (file)
@@ -1162,8 +1162,11 @@ settings.default.complex_value=<complex value>
 settings.default.password=<password>
 settings.reset_confirm.title=Reset Setting
 settings.reset_confirm.description=Are you sure that you want to reset this setting?
+settings.definition.reset=Reset "{0}" to default values
+settings.definition.delete_value=Delete value "{1}" for setting "{0}"
 
 settings.search.placeholder=Find in Settings
+settings.search.results=Search results list
 
 settings.json.format=Format JSON
 settings.json.format_error=Formatting requires valid JSON. Please fix it and retry.