]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18593 Migrate rtl for settings
authorstanislavh <stanislav.honcharov@sonarsource.com>
Fri, 3 Mar 2023 12:48:04 +0000 (13:48 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 6 Mar 2023 20:03:37 +0000 (20:03 +0000)
74 files changed:
server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts
server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx
server/sonar-web/src/main/js/apps/overview/branches/ProjectLeakPeriodInfo.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/ProjectLeakPeriodInfo-test.tsx
server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingReferenceBranch.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingAnalysis-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingDays-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingPreviousVersion-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingReferenceBranch-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchBaselineSettingModal-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchList-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchListRow-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineApp-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/ProjectBaselineApp-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/projectBaseline/constants.ts [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx
server/sonar-web/src/main/js/apps/projectBaseline/utils.ts
server/sonar-web/src/main/js/apps/settings/components/Definition.tsx
server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-it.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForFormattedText.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForFormattedText-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/InputForJSON-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/controls/Toggle.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Toggle-test.tsx.snap
server/sonar-web/src/main/js/components/controls/buttons.tsx
server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts
server/sonar-web/src/main/js/helpers/mocks/new-code-period.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/mocks/settings.ts
server/sonar-web/src/main/js/helpers/periods.ts
server/sonar-web/src/main/js/types/settings.ts
server/sonar-web/src/main/js/types/types.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts
new file mode 100644 (file)
index 0000000..df8b3b6
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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 { cloneDeep } from 'lodash';
+import { mockNewCodePeriod } from '../../helpers/mocks/new-code-period';
+import { NewCodePeriod, NewCodePeriodSettingType } from '../../types/types';
+import { getNewCodePeriod, setNewCodePeriod } from '../newCodePeriod';
+
+jest.mock('../newCodePeriod');
+
+const defaultNewCodePeriod = mockNewCodePeriod();
+
+export default class NewCodePeriodsServiceMock {
+  #newCodePeriod: NewCodePeriod;
+
+  constructor() {
+    this.#newCodePeriod = cloneDeep(defaultNewCodePeriod);
+    jest.mocked(getNewCodePeriod).mockImplementation(this.handleGetNewCodePeriod);
+    jest.mocked(setNewCodePeriod).mockImplementation(this.handleSetNewCodePeriod);
+  }
+
+  handleGetNewCodePeriod = () => {
+    return this.reply(this.#newCodePeriod);
+  };
+
+  handleSetNewCodePeriod = (data: {
+    project?: string;
+    branch?: string;
+    type: NewCodePeriodSettingType;
+    value?: string;
+  }) => {
+    const { type, value } = data;
+    this.#newCodePeriod = mockNewCodePeriod({ type, value });
+    return this.reply(undefined);
+  };
+
+  reset = () => {
+    this.#newCodePeriod = cloneDeep(defaultNewCodePeriod);
+  };
+
+  reply<T>(response: T): Promise<T> {
+    return Promise.resolve(cloneDeep(response));
+  }
+}
index abfd964dcb8df01fb493bf7ad170b7ae502080b2..b22051af84a9a68a69a8a29563636ab8838fcf07 100644 (file)
@@ -17,9 +17,9 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { cloneDeep } from 'lodash';
+import { cloneDeep, isArray, isObject, isString } from 'lodash';
 import { HousekeepingPolicy } from '../../apps/audit-logs/utils';
-import { mockDefinition } from '../../helpers/mocks/settings';
+import { mockDefinition, mockSettingFieldDefinition } from '../../helpers/mocks/settings';
 import { BranchParameters } from '../../types/branch-like';
 import {
   ExtendedSettingDefinition,
@@ -37,7 +37,8 @@ import {
   setSettingValue,
 } from '../settings';
 
-const isEmptyString = (i: string) => i.trim() === '';
+const isEmptyField = (o: any) => isObject(o) && Object.values(o).some(isEmptyString);
+const isEmptyString = (i: any) => isString(i) && i.trim() === '';
 
 export const DEFAULT_DEFINITIONS_MOCK = [
   mockDefinition({
@@ -46,7 +47,6 @@ export const DEFAULT_DEFINITIONS_MOCK = [
     subCategory: 'Announcement',
     name: 'Announcement message',
     description: 'Enter message',
-    defaultValue: '.js,.jsx,.cjs,.vue,.mjs',
     type: SettingType.TEXT,
   }),
   mockDefinition({
@@ -55,7 +55,6 @@ export const DEFAULT_DEFINITIONS_MOCK = [
     subCategory: 'Compute Engine',
     name: 'Run analysis in paralel',
     description: 'Enter message',
-    defaultValue: '.js,.jsx,.cjs,.vue,.mjs',
     type: SettingType.TEXT,
   }),
   mockDefinition({
@@ -84,6 +83,18 @@ export const DEFAULT_DEFINITIONS_MOCK = [
     description: 'Paths to xml files',
     multiValues: true,
   }),
+  mockDefinition({
+    category: 'COBOL',
+    key: 'sonar.cobol.compilationConstants',
+    subCategory: 'Preprocessor',
+    name: 'Compilation Constants',
+    description: 'Lets do it',
+    type: SettingType.PROPERTY_SET,
+    fields: [
+      mockSettingFieldDefinition(),
+      mockSettingFieldDefinition({ key: 'value', name: 'Value' }),
+    ],
+  }),
 ];
 
 export default class SettingsServiceMock {
@@ -131,8 +142,9 @@ export default class SettingsServiceMock {
 
   handleSetSettingValue = (definition: SettingDefinition, value: any): Promise<void> => {
     if (
-      (typeof value === 'string' && isEmptyString(value)) ||
-      (value instanceof Array && value.some(isEmptyString))
+      isEmptyString(value) ||
+      (isArray(value) && value.some(isEmptyString)) ||
+      isEmptyField(value)
     ) {
       throw new ResponseError('validation error', {
         errors: [{ msg: 'A non empty value must be provided' }],
@@ -149,7 +161,9 @@ export default class SettingsServiceMock {
     const definition = this.#definitions.find(
       (d) => d.key === data.keys
     ) as ExtendedSettingDefinition;
-    if (definition.multiValues === true) {
+    if (definition.type === SettingType.PROPERTY_SET) {
+      setting.fieldValues = [];
+    } else if (definition.multiValues === true) {
       setting.values = definition.defaultValue?.split(',') ?? [];
     } else {
       setting.value = definition.defaultValue ?? '';
@@ -168,12 +182,17 @@ export default class SettingsServiceMock {
     if (setting) {
       setting.value = value;
       setting.values = value;
+      setting.fieldValues = value;
     } else {
-      this.#settingValues.push({ key, value });
+      this.#settingValues.push({ key, value, values: value, fieldValues: value });
     }
     return this;
   };
 
+  setDefinition = (definition: ExtendedSettingDefinition) => {
+    this.#definitions.push(definition);
+  };
+
   reset = () => {
     this.#settingValues = cloneDeep(this.#defaultValues);
     this.#definitions = cloneDeep(DEFAULT_DEFINITIONS_MOCK);
index 02ec79d5a7458a7e0719d527548e4be3acd5f362..a740d3478797b28eff5c3cf77d7f2dad6f306203 100644 (file)
@@ -236,7 +236,7 @@ describe('profile page', () => {
     const user = userEvent.setup();
     renderAccountApp(mockLoggedInUser());
 
-    const toggle = screen.getByRole('button', {
+    const toggle = screen.getByRole('switch', {
       name: 'my_account.preferences.keyboard_shortcuts.enabled',
     });
     expect(toggle).toBeInTheDocument();
index 812d7f22130700aa7c64eb4bccd43db710efaaf2..f6f5acb46a526d7867ece964328da2681defce29 100644 (file)
@@ -27,7 +27,7 @@ import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter, { formatterOption } from '../../../components/intl/DateTimeFormatter';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods';
-import { ComponentMeasure, Period } from '../../../types/types';
+import { ComponentMeasure, NewCodePeriodSettingType, Period } from '../../../types/types';
 
 interface Props {
   className?: string;
@@ -65,7 +65,7 @@ export class LeakPeriodLegend extends React.PureComponent<Props & WrappedCompone
       </div>
     );
 
-    if (period.mode === 'days' || period.mode === 'NUMBER_OF_DAYS') {
+    if (period.mode === 'days' || period.mode === NewCodePeriodSettingType.NUMBER_OF_DAYS) {
       return label;
     }
 
index e75f4fe0a6a1a446ddf7d266598dc73ae51dbcdf..37d8c2ac37f0e31f29ef7d59ba17d783618ab7cf 100644 (file)
@@ -27,7 +27,7 @@ import { getBaseUrl } from '../../../helpers/system';
 import { queryToSearch } from '../../../helpers/urls';
 import { Branch } from '../../../types/branch-like';
 import { ComponentQualifier } from '../../../types/component';
-import { Component, Period } from '../../../types/types';
+import { Component, NewCodePeriodSettingType, Period } from '../../../types/types';
 
 export interface MeasuresPanelNoNewCodeProps {
   branch?: Branch;
@@ -41,7 +41,7 @@ export default function MeasuresPanelNoNewCode(props: MeasuresPanelNoNewCodeProp
   const isApp = component.qualifier === ComponentQualifier.Application;
 
   const hasBadReferenceBranch =
-    !isApp && !!period && !period.date && period.mode === 'REFERENCE_BRANCH';
+    !isApp && !!period && !period.date && period.mode === NewCodePeriodSettingType.REFERENCE_BRANCH;
   /*
    * If the period is "reference branch"-based, and if there's no date, it means
    * that we're not lacking a second analysis, but that we'll never have new code because the
index 51eadd8dc84449516a08da9cdeeff80ac5be5ec9..ec76ddfafde26cda82bd1257b89a7c4dbd6c4255 100644 (file)
@@ -24,7 +24,7 @@ import DateFromNow from '../../../components/intl/DateFromNow';
 import { formatterOption } from '../../../components/intl/DateTimeFormatter';
 import { translateWithParameters } from '../../../helpers/l10n';
 import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods';
-import { Period } from '../../../types/types';
+import { NewCodePeriodSettingType, Period } from '../../../types/types';
 
 export interface ProjectLeakPeriodInfoProps extends WrappedComponentProps {
   leakPeriod: Period;
@@ -38,7 +38,7 @@ export function ProjectLeakPeriodInfo(props: ProjectLeakPeriodInfoProps) {
 
   const leakPeriodLabel = getPeriodLabel(
     leakPeriod,
-    ['manual_baseline', 'SPECIFIC_ANALYSIS'].includes(leakPeriod.mode)
+    ['manual_baseline', NewCodePeriodSettingType.SPECIFIC_ANALYSIS].includes(leakPeriod.mode)
       ? (date: string) => formatTime(date, formatterOption)
       : (date: string) => formatDate(date, longFormatterOption)
   );
@@ -49,8 +49,8 @@ export function ProjectLeakPeriodInfo(props: ProjectLeakPeriodInfoProps) {
 
   if (
     leakPeriod.mode === 'days' ||
-    leakPeriod.mode === 'NUMBER_OF_DAYS' ||
-    leakPeriod.mode === 'REFERENCE_BRANCH'
+    leakPeriod.mode === NewCodePeriodSettingType.NUMBER_OF_DAYS ||
+    leakPeriod.mode === NewCodePeriodSettingType.REFERENCE_BRANCH
   ) {
     return <div className="note spacer-top">{leakPeriodLabel} </div>;
   }
index fe635fe21149bd5acfdd70b473784d57d8234a0b..516922ab1830d8064f9b64009c066eecc4e9329f 100644 (file)
@@ -23,7 +23,7 @@ import * as React from 'react';
 import { IntlShape } from 'react-intl';
 import { mockPeriod } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import { Period } from '../../../../types/types';
+import { NewCodePeriodSettingType, Period } from '../../../../types/types';
 import { ProjectLeakPeriodInfo } from '../ProjectLeakPeriodInfo';
 
 jest.mock('date-fns', () => {
@@ -62,7 +62,7 @@ it('should render correctly for "previous_analysis"', async () => {
 
 it('should render correctly for "REFERENCE_BRANCH"', async () => {
   renderProjectLeakPeriodInfo({
-    mode: 'REFERENCE_BRANCH',
+    mode: NewCodePeriodSettingType.REFERENCE_BRANCH,
     parameter: 'master',
   });
   expect(await screen.findByText('overview.period.reference_branch.master')).toBeInTheDocument();
index 86c20c726a5a18822fd1cb1fb0af3b9348dbdd8c..43fe036e6be2815e44aba956b18312c5a27009d1 100644 (file)
@@ -26,7 +26,7 @@ import DateFromNow from '../../../components/intl/DateFromNow';
 import DateTimeFormatter, { formatterOption } from '../../../components/intl/DateTimeFormatter';
 import { translateWithParameters } from '../../../helpers/l10n';
 import { getPeriodDate, getPeriodLabel } from '../../../helpers/periods';
-import { Dict, Period } from '../../../types/types';
+import { Dict, NewCodePeriodSettingType, Period } from '../../../types/types';
 
 interface Props {
   period: Period;
@@ -56,7 +56,7 @@ export class LeakPeriodLegend extends React.PureComponent<Props & WrappedCompone
       return null;
     }
 
-    if (period.mode === 'days' || period.mode === 'NUMBER_OF_DAYS') {
+    if (period.mode === 'days' || period.mode === NewCodePeriodSettingType.NUMBER_OF_DAYS) {
       return (
         <div className="overview-legend overview-legend-spaced-line">
           {translateWithParameters('overview.new_code_period_x', leakPeriodLabel)}
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
deleted file mode 100644 (file)
index 80dbcf8..0000000
+++ /dev/null
@@ -1,325 +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 classNames from 'classnames';
-import { debounce } from 'lodash';
-import * as React from 'react';
-import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import withAvailableFeatures, {
-  WithAvailableFeaturesProps,
-} from '../../../app/components/available-features/withAvailableFeatures';
-import withComponentContext from '../../../app/components/componentContext/withComponentContext';
-import Suggestions from '../../../components/embed-docs-modal/Suggestions';
-import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
-import DeferredSpinner from '../../../components/ui/DeferredSpinner';
-import { isBranch, sortBranches } from '../../../helpers/branch-like';
-import { translate } from '../../../helpers/l10n';
-import { AppState } from '../../../types/appstate';
-import { Branch, BranchLike } from '../../../types/branch-like';
-import { Feature } from '../../../types/features';
-import { ParsedAnalysis } from '../../../types/project-activity';
-import { Component, NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
-import '../styles.css';
-import { getSettingValue } from '../utils';
-import AppHeader from './AppHeader';
-import BranchList from './BranchList';
-import ProjectBaselineSelector from './ProjectBaselineSelector';
-
-interface Props extends WithAvailableFeaturesProps {
-  branchLike: Branch;
-  branchLikes: BranchLike[];
-  component: Component;
-  appState: AppState;
-}
-
-interface State {
-  analysis?: string;
-  branchList: Branch[];
-  currentSetting?: NewCodePeriodSettingType;
-  currentSettingValue?: string;
-  days: string;
-  generalSetting?: NewCodePeriod;
-  loading: boolean;
-  overrideGeneralSetting?: boolean;
-  referenceBranch?: string;
-  saving: boolean;
-  selected?: NewCodePeriodSettingType;
-  success?: boolean;
-}
-
-const DEFAULT_NUMBER_OF_DAYS = '30';
-
-const DEFAULT_GENERAL_SETTING: { type: NewCodePeriodSettingType } = {
-  type: 'PREVIOUS_VERSION',
-};
-
-export class App extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = {
-    branchList: [],
-    days: DEFAULT_NUMBER_OF_DAYS,
-    loading: true,
-    saving: false,
-  };
-
-  // We use debounce as we could have multiple save in less that 3sec.
-  resetSuccess = debounce(() => this.setState({ success: undefined }), 3000);
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchLeakPeriodSetting();
-    this.sortAndFilterBranches(this.props.branchLikes);
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.branchLikes !== this.props.branchLikes) {
-      this.sortAndFilterBranches(this.props.branchLikes);
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  getUpdatedState(params: {
-    currentSetting?: NewCodePeriodSettingType;
-    currentSettingValue?: string;
-    generalSetting: NewCodePeriod;
-  }) {
-    const { currentSetting, currentSettingValue, generalSetting } = params;
-    const { referenceBranch } = this.state;
-
-    const defaultDays =
-      (generalSetting.type === 'NUMBER_OF_DAYS' && generalSetting.value) || DEFAULT_NUMBER_OF_DAYS;
-
-    return {
-      loading: false,
-      currentSetting,
-      currentSettingValue,
-      generalSetting,
-      selected: currentSetting || generalSetting.type,
-      overrideGeneralSetting: Boolean(currentSetting),
-      days: (currentSetting === 'NUMBER_OF_DAYS' && currentSettingValue) || defaultDays,
-      analysis: (currentSetting === 'SPECIFIC_ANALYSIS' && currentSettingValue) || '',
-      referenceBranch:
-        (currentSetting === 'REFERENCE_BRANCH' && currentSettingValue) || referenceBranch,
-    };
-  }
-
-  sortAndFilterBranches(branchLikes: BranchLike[] = []) {
-    const branchList = sortBranches(branchLikes.filter(isBranch));
-    this.setState({ branchList, referenceBranch: branchList[0].name });
-  }
-
-  fetchLeakPeriodSetting() {
-    const { branchLike, component } = this.props;
-
-    this.setState({ loading: true });
-
-    Promise.all([
-      getNewCodePeriod(),
-      getNewCodePeriod({
-        branch: this.props.hasFeature(Feature.BranchSupport) ? undefined : branchLike.name,
-        project: component.key,
-      }),
-    ]).then(
-      ([generalSetting, setting]) => {
-        if (this.mounted) {
-          if (!generalSetting.type) {
-            generalSetting = DEFAULT_GENERAL_SETTING;
-          }
-          const currentSettingValue = setting.value;
-          const currentSetting = setting.inherited ? undefined : setting.type || 'PREVIOUS_VERSION';
-
-          this.setState(
-            this.getUpdatedState({
-              generalSetting,
-              currentSetting,
-              currentSettingValue,
-            })
-          );
-        }
-      },
-      () => {
-        this.setState({ loading: false });
-      }
-    );
-  }
-
-  resetSetting = () => {
-    this.setState({ saving: true });
-    resetNewCodePeriod({ project: this.props.component.key }).then(
-      () => {
-        this.setState({
-          saving: false,
-          currentSetting: undefined,
-          selected: undefined,
-          success: true,
-        });
-        this.resetSuccess();
-      },
-      () => {
-        this.setState({ saving: false });
-      }
-    );
-  };
-
-  handleSelectAnalysis = (analysis: ParsedAnalysis) => this.setState({ analysis: analysis.key });
-
-  handleSelectDays = (days: string) => this.setState({ days });
-
-  handleSelectReferenceBranch = (referenceBranch: string) => {
-    this.setState({ referenceBranch });
-  };
-
-  handleCancel = () =>
-    this.setState(
-      ({ generalSetting = DEFAULT_GENERAL_SETTING, currentSetting, currentSettingValue }) =>
-        this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue })
-    );
-
-  handleSelectSetting = (selected?: NewCodePeriodSettingType) => this.setState({ selected });
-
-  handleToggleSpecificSetting = (overrideGeneralSetting: boolean) =>
-    this.setState({ overrideGeneralSetting });
-
-  handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
-    e.preventDefault();
-
-    const { component } = this.props;
-    const { analysis, days, selected: type, referenceBranch, overrideGeneralSetting } = this.state;
-
-    if (!overrideGeneralSetting) {
-      this.resetSetting();
-      return;
-    }
-
-    const value = getSettingValue({ type, analysis, days, referenceBranch });
-
-    if (type) {
-      this.setState({ saving: true });
-      setNewCodePeriod({
-        project: component.key,
-        type,
-        value,
-      }).then(
-        () => {
-          this.setState({
-            saving: false,
-            currentSetting: type,
-            currentSettingValue: value || undefined,
-            success: true,
-          });
-          this.resetSuccess();
-        },
-        () => {
-          this.setState({ saving: false });
-        }
-      );
-    }
-  };
-
-  render() {
-    const { appState, component, branchLike } = this.props;
-    const {
-      analysis,
-      branchList,
-      currentSetting,
-      days,
-      generalSetting,
-      loading,
-      currentSettingValue,
-      overrideGeneralSetting,
-      referenceBranch,
-      saving,
-      selected,
-      success,
-    } = this.state;
-    const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
-
-    return (
-      <>
-        <Suggestions suggestions="project_baseline" />
-        <div className="page page-limited">
-          <AppHeader canAdmin={!!appState.canAdmin} />
-          {loading ? (
-            <DeferredSpinner />
-          ) : (
-            <div className="panel-white project-baseline">
-              {branchSupportEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
-
-              {generalSetting && overrideGeneralSetting !== undefined && (
-                <ProjectBaselineSelector
-                  analysis={analysis}
-                  branch={branchLike}
-                  branchList={branchList}
-                  branchesEnabled={branchSupportEnabled}
-                  component={component.key}
-                  currentSetting={currentSetting}
-                  currentSettingValue={currentSettingValue}
-                  days={days}
-                  generalSetting={generalSetting}
-                  onCancel={this.handleCancel}
-                  onSelectAnalysis={this.handleSelectAnalysis}
-                  onSelectDays={this.handleSelectDays}
-                  onSelectReferenceBranch={this.handleSelectReferenceBranch}
-                  onSelectSetting={this.handleSelectSetting}
-                  onSubmit={this.handleSubmit}
-                  onToggleSpecificSetting={this.handleToggleSpecificSetting}
-                  overrideGeneralSetting={overrideGeneralSetting}
-                  referenceBranch={referenceBranch}
-                  saving={saving}
-                  selected={selected}
-                />
-              )}
-
-              <div className={classNames('spacer-top', { invisible: saving || !success })}>
-                <span className="text-success">
-                  <AlertSuccessIcon className="spacer-right" />
-                  {translate('settings.state.saved')}
-                </span>
-              </div>
-              {generalSetting && branchSupportEnabled && (
-                <div className="huge-spacer-top branch-baseline-selector">
-                  <hr />
-                  <h2>{translate('project_baseline.configure_branches')}</h2>
-                  <BranchList
-                    branchList={branchList}
-                    component={component}
-                    inheritedSetting={
-                      currentSetting
-                        ? {
-                            type: currentSetting,
-                            value: currentSettingValue,
-                          }
-                        : generalSetting
-                    }
-                  />
-                </div>
-              )}
-            </div>
-          )}
-        </div>
-      </>
-    );
-  }
-}
-
-export default withComponentContext(withAvailableFeatures(withAppStateContext(App)));
index f31cd3087282597e5e34279b7ec944c53f53dd21..0528174c70c3ae9a506215d6c6d8f0c5faaa3ad4 100644 (file)
@@ -32,7 +32,7 @@ export default function BaselineSettingAnalysis({ disabled, onSelect, selected }
   return (
     <RadioCard
       disabled={disabled}
-      onClick={() => onSelect('SPECIFIC_ANALYSIS')}
+      onClick={() => onSelect(NewCodePeriodSettingType.SPECIFIC_ANALYSIS)}
       selected={selected}
       title={translate('baseline.specific_analysis')}
     >
index 0f356bfd88009868eac8587a6eb618a7dd8c79ef..ef42354448d4818ac6dda7cc32fd9c9735e42ddc 100644 (file)
@@ -41,7 +41,7 @@ export default function BaselineSettingDays(props: Props) {
     <RadioCard
       className={className}
       disabled={disabled}
-      onClick={() => onSelect('NUMBER_OF_DAYS')}
+      onClick={() => onSelect(NewCodePeriodSettingType.NUMBER_OF_DAYS)}
       selected={selected}
       title={translate('baseline.number_days')}
     >
index 5084aa5a124575b13e96fdfe1b1536c3a1468c1f..e0ccc8487f90c2287f9f772f0649cd94e92d9310 100644 (file)
@@ -34,7 +34,7 @@ export default function BaselineSettingPreviousVersion(props: Props) {
   return (
     <RadioCard
       disabled={disabled}
-      onClick={() => onSelect('PREVIOUS_VERSION')}
+      onClick={() => onSelect(NewCodePeriodSettingType.PREVIOUS_VERSION)}
       selected={selected}
       title={
         translate('baseline.previous_version') + (isDefault ? ` (${translate('default')})` : '')
index 35600e31718ea91cb79f90da181dea19fc0fb3de..6604d1e39dadec71a4d93aefd1be03aedd111e9f 100644 (file)
@@ -98,7 +98,7 @@ export default function BaselineSettingReferenceBranch(props: BaselineSettingRef
     <RadioCard
       className={className}
       disabled={disabled}
-      onClick={() => props.onSelect('REFERENCE_BRANCH')}
+      onClick={() => props.onSelect(NewCodePeriodSettingType.REFERENCE_BRANCH)}
       selected={selected}
       title={translate('baseline.reference_branch')}
     >
index a9236fe447ee2a0984b3273b447576128d5ae673..0a3ec8523297c0d84cb16c0c72cbb8a679b71088 100644 (file)
@@ -60,9 +60,10 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop
     const defaultBranch = otherBranches.length > 0 ? otherBranches[0].name : '';
 
     this.state = {
-      analysis: this.getValueFromProps('SPECIFIC_ANALYSIS') || '',
-      days: this.getValueFromProps('NUMBER_OF_DAYS') || '30',
-      referenceBranch: this.getValueFromProps('REFERENCE_BRANCH') || defaultBranch,
+      analysis: this.getValueFromProps(NewCodePeriodSettingType.SPECIFIC_ANALYSIS) || '',
+      days: this.getValueFromProps(NewCodePeriodSettingType.NUMBER_OF_DAYS) || '30',
+      referenceBranch:
+        this.getValueFromProps(NewCodePeriodSettingType.REFERENCE_BRANCH) || defaultBranch,
       saving: false,
       selected: this.props.branch.newCodePeriod && this.props.branch.newCodePeriod.type,
     };
@@ -164,7 +165,7 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop
               <BaselineSettingPreviousVersion
                 isDefault={false}
                 onSelect={this.handleSelectSetting}
-                selected={selected === 'PREVIOUS_VERSION'}
+                selected={selected === NewCodePeriodSettingType.PREVIOUS_VERSION}
               />
               <BaselineSettingDays
                 days={days}
@@ -172,22 +173,22 @@ export default class BranchBaselineSettingModal extends React.PureComponent<Prop
                 isValid={isValid}
                 onChangeDays={this.handleSelectDays}
                 onSelect={this.handleSelectSetting}
-                selected={selected === 'NUMBER_OF_DAYS'}
+                selected={selected === NewCodePeriodSettingType.NUMBER_OF_DAYS}
               />
               <BaselineSettingAnalysis
                 onSelect={this.handleSelectSetting}
-                selected={selected === 'SPECIFIC_ANALYSIS'}
+                selected={selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS}
               />
               <BaselineSettingReferenceBranch
                 branchList={branchList.map(this.branchToOption)}
                 onChangeReferenceBranch={this.handleSelectReferenceBranch}
                 onSelect={this.handleSelectSetting}
                 referenceBranch={referenceBranch}
-                selected={selected === 'REFERENCE_BRANCH'}
+                selected={selected === NewCodePeriodSettingType.REFERENCE_BRANCH}
                 settingLevel="branch"
               />
             </div>
-            {selected === 'SPECIFIC_ANALYSIS' && (
+            {selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && (
               <BranchAnalysisList
                 analysis={analysis}
                 branch={branch.name}
index a3a5bf7f2dc12e6aaf5c67496423fd4b889d82be..7d9943686e52bab330303ccf87f6897857b972ae 100644 (file)
@@ -24,6 +24,7 @@ import { isBranch, sortBranches } from '../../../helpers/branch-like';
 import { translate } from '../../../helpers/l10n';
 import { Branch, BranchLike, BranchWithNewCodePeriod } from '../../../types/branch-like';
 import { Component, NewCodePeriod } from '../../../types/types';
+import { DEFAULT_GENERAL_SETTING_TYPE } from '../constants';
 import BranchBaselineSettingModal from './BranchBaselineSettingModal';
 import BranchListRow from './BranchListRow';
 
@@ -74,7 +75,7 @@ export default class BranchList extends React.PureComponent<Props, State> {
           if (!newCodePeriod) {
             return b;
           }
-          const { type = 'PREVIOUS_VERSION', value, effectiveValue } = newCodePeriod;
+          const { type = DEFAULT_GENERAL_SETTING_TYPE, value, effectiveValue } = newCodePeriod;
           return {
             ...b,
             newCodePeriod: { type, value, effectiveValue },
index 9ed131753c9d375f5c03b37b0f5b41f56ee55939..ff07fee94dd4dee6db845069e4bf3e68113178f1 100644 (file)
@@ -25,7 +25,7 @@ import WarningIcon from '../../../components/icons/WarningIcon';
 import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { BranchWithNewCodePeriod } from '../../../types/branch-like';
-import { NewCodePeriod } from '../../../types/types';
+import { NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
 
 export interface BranchListRowProps {
   branch: BranchWithNewCodePeriod;
@@ -37,7 +37,7 @@ export interface BranchListRowProps {
 
 function renderNewCodePeriodSetting(newCodePeriod: NewCodePeriod) {
   switch (newCodePeriod.type) {
-    case 'SPECIFIC_ANALYSIS':
+    case NewCodePeriodSettingType.SPECIFIC_ANALYSIS:
       return (
         <>
           {`${translate('baseline.specific_analysis')}: `}
@@ -48,11 +48,11 @@ function renderNewCodePeriodSetting(newCodePeriod: NewCodePeriod) {
           )}
         </>
       );
-    case 'NUMBER_OF_DAYS':
+    case NewCodePeriodSettingType.NUMBER_OF_DAYS:
       return `${translate('baseline.number_days')}: ${newCodePeriod.value}`;
-    case 'PREVIOUS_VERSION':
+    case NewCodePeriodSettingType.PREVIOUS_VERSION:
       return translate('baseline.previous_version');
-    case 'REFERENCE_BRANCH':
+    case NewCodePeriodSettingType.REFERENCE_BRANCH:
       return `${translate('baseline.reference_branch')}: ${newCodePeriod.value}`;
     default:
       return newCodePeriod.type;
@@ -65,7 +65,7 @@ function branchInheritsItselfAsReference(
 ) {
   return (
     !branch.newCodePeriod &&
-    inheritedSetting.type === 'REFERENCE_BRANCH' &&
+    inheritedSetting.type === NewCodePeriodSettingType.REFERENCE_BRANCH &&
     branch.name === inheritedSetting.value
   );
 }
@@ -77,7 +77,7 @@ function referenceBranchDoesNotExist(
   return (
     branch.newCodePeriod &&
     branch.newCodePeriod.value &&
-    branch.newCodePeriod.type === 'REFERENCE_BRANCH' &&
+    branch.newCodePeriod.type === NewCodePeriodSettingType.REFERENCE_BRANCH &&
     !existingBranches.includes(branch.newCodePeriod.value)
   );
 }
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx
new file mode 100644 (file)
index 0000000..025cc21
--- /dev/null
@@ -0,0 +1,333 @@
+/*
+ * 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 classNames from 'classnames';
+import { debounce } from 'lodash';
+import * as React from 'react';
+import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+  WithAvailableFeaturesProps,
+} from '../../../app/components/available-features/withAvailableFeatures';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
+import DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { isBranch, sortBranches } from '../../../helpers/branch-like';
+import { translate } from '../../../helpers/l10n';
+import { AppState } from '../../../types/appstate';
+import { Branch, BranchLike } from '../../../types/branch-like';
+import { Feature } from '../../../types/features';
+import { ParsedAnalysis } from '../../../types/project-activity';
+import { Component, NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
+import { DEFAULT_GENERAL_SETTING_TYPE } from '../constants';
+import '../styles.css';
+import { getSettingValue } from '../utils';
+import AppHeader from './AppHeader';
+import BranchList from './BranchList';
+import ProjectBaselineSelector from './ProjectBaselineSelector';
+
+interface Props extends WithAvailableFeaturesProps {
+  branchLike: Branch;
+  branchLikes: BranchLike[];
+  component: Component;
+  appState: AppState;
+}
+
+interface State {
+  analysis?: string;
+  branchList: Branch[];
+  currentSetting?: NewCodePeriodSettingType;
+  currentSettingValue?: string;
+  days: string;
+  generalSetting?: NewCodePeriod;
+  loading: boolean;
+  overrideGeneralSetting?: boolean;
+  referenceBranch?: string;
+  saving: boolean;
+  selected?: NewCodePeriodSettingType;
+  success?: boolean;
+}
+
+const DEFAULT_NUMBER_OF_DAYS = '30';
+
+export class ProjectBaselineApp extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = {
+    branchList: [],
+    days: DEFAULT_NUMBER_OF_DAYS,
+    loading: true,
+    saving: false,
+  };
+
+  // We use debounce as we could have multiple save in less that 3sec.
+  resetSuccess = debounce(() => this.setState({ success: undefined }), 3000);
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchLeakPeriodSetting();
+    this.sortAndFilterBranches(this.props.branchLikes);
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.branchLikes !== this.props.branchLikes) {
+      this.sortAndFilterBranches(this.props.branchLikes);
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  getUpdatedState(params: {
+    currentSetting?: NewCodePeriodSettingType;
+    currentSettingValue?: string;
+    generalSetting: NewCodePeriod;
+  }) {
+    const { currentSetting, currentSettingValue, generalSetting } = params;
+    const { referenceBranch } = this.state;
+
+    const defaultDays =
+      (generalSetting.type === NewCodePeriodSettingType.NUMBER_OF_DAYS && generalSetting.value) ||
+      DEFAULT_NUMBER_OF_DAYS;
+
+    return {
+      loading: false,
+      currentSetting,
+      currentSettingValue,
+      generalSetting,
+      selected: currentSetting || generalSetting.type,
+      overrideGeneralSetting: Boolean(currentSetting),
+      days:
+        (currentSetting === NewCodePeriodSettingType.NUMBER_OF_DAYS && currentSettingValue) ||
+        defaultDays,
+      analysis:
+        (currentSetting === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && currentSettingValue) ||
+        '',
+      referenceBranch:
+        (currentSetting === NewCodePeriodSettingType.REFERENCE_BRANCH && currentSettingValue) ||
+        referenceBranch,
+    };
+  }
+
+  sortAndFilterBranches(branchLikes: BranchLike[] = []) {
+    const branchList = sortBranches(branchLikes.filter(isBranch));
+    this.setState({ branchList, referenceBranch: branchList[0].name });
+  }
+
+  fetchLeakPeriodSetting() {
+    const { branchLike, component } = this.props;
+
+    this.setState({ loading: true });
+
+    Promise.all([
+      getNewCodePeriod(),
+      getNewCodePeriod({
+        branch: this.props.hasFeature(Feature.BranchSupport) ? undefined : branchLike.name,
+        project: component.key,
+      }),
+    ]).then(
+      ([generalSetting, setting]) => {
+        if (this.mounted) {
+          if (!generalSetting.type) {
+            generalSetting = { type: DEFAULT_GENERAL_SETTING_TYPE };
+          }
+          const currentSettingValue = setting.value;
+          const currentSetting = setting.inherited
+            ? undefined
+            : setting.type || DEFAULT_GENERAL_SETTING_TYPE;
+
+          this.setState(
+            this.getUpdatedState({
+              generalSetting,
+              currentSetting,
+              currentSettingValue,
+            })
+          );
+        }
+      },
+      () => {
+        this.setState({ loading: false });
+      }
+    );
+  }
+
+  resetSetting = () => {
+    this.setState({ saving: true });
+    resetNewCodePeriod({ project: this.props.component.key }).then(
+      () => {
+        this.setState({
+          saving: false,
+          currentSetting: undefined,
+          selected: undefined,
+          success: true,
+        });
+        this.resetSuccess();
+      },
+      () => {
+        this.setState({ saving: false });
+      }
+    );
+  };
+
+  handleSelectAnalysis = (analysis: ParsedAnalysis) => this.setState({ analysis: analysis.key });
+
+  handleSelectDays = (days: string) => this.setState({ days });
+
+  handleSelectReferenceBranch = (referenceBranch: string) => {
+    this.setState({ referenceBranch });
+  };
+
+  handleCancel = () =>
+    this.setState(
+      ({
+        generalSetting = { type: DEFAULT_GENERAL_SETTING_TYPE },
+        currentSetting,
+        currentSettingValue,
+      }) => this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue })
+    );
+
+  handleSelectSetting = (selected?: NewCodePeriodSettingType) => this.setState({ selected });
+
+  handleToggleSpecificSetting = (overrideGeneralSetting: boolean) =>
+    this.setState({ overrideGeneralSetting });
+
+  handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
+    e.preventDefault();
+
+    const { component } = this.props;
+    const { analysis, days, selected: type, referenceBranch, overrideGeneralSetting } = this.state;
+
+    if (!overrideGeneralSetting) {
+      this.resetSetting();
+      return;
+    }
+
+    const value = getSettingValue({ type, analysis, days, referenceBranch });
+
+    if (type) {
+      this.setState({ saving: true });
+      setNewCodePeriod({
+        project: component.key,
+        type,
+        value,
+      }).then(
+        () => {
+          this.setState({
+            saving: false,
+            currentSetting: type,
+            currentSettingValue: value || undefined,
+            success: true,
+          });
+          this.resetSuccess();
+        },
+        () => {
+          this.setState({ saving: false });
+        }
+      );
+    }
+  };
+
+  render() {
+    const { appState, component, branchLike } = this.props;
+    const {
+      analysis,
+      branchList,
+      currentSetting,
+      days,
+      generalSetting,
+      loading,
+      currentSettingValue,
+      overrideGeneralSetting,
+      referenceBranch,
+      saving,
+      selected,
+      success,
+    } = this.state;
+    const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
+
+    return (
+      <>
+        <Suggestions suggestions="project_baseline" />
+        <div className="page page-limited">
+          <AppHeader canAdmin={!!appState.canAdmin} />
+          {loading ? (
+            <DeferredSpinner />
+          ) : (
+            <div className="panel-white project-baseline">
+              {branchSupportEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
+
+              {generalSetting && overrideGeneralSetting !== undefined && (
+                <ProjectBaselineSelector
+                  analysis={analysis}
+                  branch={branchLike}
+                  branchList={branchList}
+                  branchesEnabled={branchSupportEnabled}
+                  component={component.key}
+                  currentSetting={currentSetting}
+                  currentSettingValue={currentSettingValue}
+                  days={days}
+                  generalSetting={generalSetting}
+                  onCancel={this.handleCancel}
+                  onSelectAnalysis={this.handleSelectAnalysis}
+                  onSelectDays={this.handleSelectDays}
+                  onSelectReferenceBranch={this.handleSelectReferenceBranch}
+                  onSelectSetting={this.handleSelectSetting}
+                  onSubmit={this.handleSubmit}
+                  onToggleSpecificSetting={this.handleToggleSpecificSetting}
+                  overrideGeneralSetting={overrideGeneralSetting}
+                  referenceBranch={referenceBranch}
+                  saving={saving}
+                  selected={selected}
+                />
+              )}
+
+              <div className={classNames('spacer-top', { invisible: saving || !success })}>
+                <span className="text-success">
+                  <AlertSuccessIcon className="spacer-right" />
+                  {translate('settings.state.saved')}
+                </span>
+              </div>
+              {generalSetting && branchSupportEnabled && (
+                <div className="huge-spacer-top branch-baseline-selector">
+                  <hr />
+                  <h2>{translate('project_baseline.configure_branches')}</h2>
+                  <BranchList
+                    branchList={branchList}
+                    component={component}
+                    inheritedSetting={
+                      currentSetting
+                        ? {
+                            type: currentSetting,
+                            value: currentSettingValue,
+                          }
+                        : generalSetting
+                    }
+                  />
+                </div>
+              )}
+            </div>
+          )}
+        </div>
+      </>
+    );
+  }
+}
+
+export default withComponentContext(withAvailableFeatures(withAppStateContext(ProjectBaselineApp)));
index 76aa0647165ded3af30117c4db05e36feba526b0..ac0eb18dbeff2e660fdf11035eebad683bf958da 100644 (file)
@@ -60,7 +60,7 @@ export interface ProjectBaselineSelectorProps {
 function renderGeneralSetting(generalSetting: NewCodePeriod) {
   let setting: string;
   let description: string;
-  if (generalSetting.type === 'NUMBER_OF_DAYS') {
+  if (generalSetting.type === NewCodePeriodSettingType.NUMBER_OF_DAYS) {
     setting = `${translate('baseline.number_days')} (${translateWithParameters(
       'duration.days',
       generalSetting.value || '?'
@@ -137,7 +137,9 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr
           <BaselineSettingPreviousVersion
             disabled={!overrideGeneralSetting}
             onSelect={props.onSelectSetting}
-            selected={overrideGeneralSetting && selected === 'PREVIOUS_VERSION'}
+            selected={
+              overrideGeneralSetting && selected === NewCodePeriodSettingType.PREVIOUS_VERSION
+            }
           />
           <BaselineSettingDays
             days={days}
@@ -146,7 +148,9 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr
             isValid={isValid}
             onChangeDays={props.onSelectDays}
             onSelect={props.onSelectSetting}
-            selected={overrideGeneralSetting && selected === 'NUMBER_OF_DAYS'}
+            selected={
+              overrideGeneralSetting && selected === NewCodePeriodSettingType.NUMBER_OF_DAYS
+            }
           />
           {branchesEnabled ? (
             <BaselineSettingReferenceBranch
@@ -155,18 +159,22 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr
               onChangeReferenceBranch={props.onSelectReferenceBranch}
               onSelect={props.onSelectSetting}
               referenceBranch={referenceBranch || ''}
-              selected={overrideGeneralSetting && selected === 'REFERENCE_BRANCH'}
+              selected={
+                overrideGeneralSetting && selected === NewCodePeriodSettingType.REFERENCE_BRANCH
+              }
               settingLevel="project"
             />
           ) : (
             <BaselineSettingAnalysis
               disabled={!overrideGeneralSetting}
               onSelect={props.onSelectSetting}
-              selected={overrideGeneralSetting && selected === 'SPECIFIC_ANALYSIS'}
+              selected={
+                overrideGeneralSetting && selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS
+              }
             />
           )}
         </div>
-        {selected === 'SPECIFIC_ANALYSIS' && (
+        {selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && (
           <BranchAnalysisList
             analysis={analysis || ''}
             branch={branch.name}
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx
deleted file mode 100644 (file)
index aaa7056..0000000
+++ /dev/null
@@ -1,118 +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,
-  resetNewCodePeriod,
-  setNewCodePeriod,
-} from '../../../../api/newCodePeriod';
-import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../helpers/testMocks';
-import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
-import { App } from '../App';
-
-jest.mock('../../../../api/newCodePeriod', () => ({
-  getNewCodePeriod: jest.fn().mockResolvedValue({}),
-  resetNewCodePeriod: jest.fn().mockResolvedValue({}),
-  setNewCodePeriod: jest.fn().mockResolvedValue({}),
-}));
-
-it('should render correctly', async () => {
-  let wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper = shallowRender({ appState: mockAppState({ canAdmin: true }), hasFeature: () => false });
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot('without branch support');
-});
-
-it('should initialize correctly', async () => {
-  const wrapper = shallowRender({
-    branchLikes: [mockBranch(), mockPullRequest(), mockMainBranch()],
-  });
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.state().branchList).toHaveLength(2);
-  expect(wrapper.state().referenceBranch).toBe('master');
-});
-
-it('should not display reset button if project setting is not set', () => {
-  const wrapper = shallowRender();
-
-  expect(wrapper.find('Button')).toHaveLength(0);
-});
-
-it('should reset the setting correctly', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  wrapper.instance().resetSetting();
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state('currentSetting')).toBeUndefined();
-  expect(wrapper.state('selected')).toBeUndefined();
-});
-
-it('should save correctly', async () => {
-  const component = mockComponent();
-  const wrapper = shallowRender({ component });
-  await waitAndUpdate(wrapper);
-  wrapper.setState({ selected: 'NUMBER_OF_DAYS', days: '23' });
-  wrapper.instance().handleSubmit(mockEvent());
-  await waitAndUpdate(wrapper);
-  expect(setNewCodePeriod).toHaveBeenCalledWith({
-    project: component.key,
-    type: 'NUMBER_OF_DAYS',
-    value: '23',
-  });
-  expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
-});
-
-it('should handle errors gracefully', async () => {
-  (getNewCodePeriod as jest.Mock).mockRejectedValue('error');
-  (setNewCodePeriod as jest.Mock).mockRejectedValue('error');
-  (resetNewCodePeriod as jest.Mock).mockRejectedValue('error');
-
-  const wrapper = shallowRender();
-  wrapper.setState({ selected: 'PREVIOUS_VERSION' });
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.state('loading')).toBe(false);
-  wrapper.instance().resetSetting();
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state('saving')).toBe(false);
-  wrapper.instance().handleSubmit(mockEvent());
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state('saving')).toBe(false);
-});
-
-function shallowRender(props: Partial<App['props']> = {}) {
-  return shallow<App>(
-    <App
-      branchLike={mockBranch()}
-      branchLikes={[mockMainBranch()]}
-      appState={mockAppState({ canAdmin: true })}
-      hasFeature={jest.fn().mockReturnValue(true)}
-      component={mockComponent()}
-      {...props}
-    />
-  );
-}
index c4b9ec1718be66295be8c63f6d2b14d86d354493..f6960dbe0fb92f475fb973afe0c6a0a815d0e421 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BaselineSettingAnalysis, { Props } from '../BaselineSettingAnalysis';
 
 it('should render correctly', () => {
@@ -30,7 +31,7 @@ it('should callback when clicked', () => {
   const wrapper = shallowRender({ onSelect, selected: false });
 
   wrapper.find('RadioCard').first().simulate('click');
-  expect(onSelect).toHaveBeenCalledWith('SPECIFIC_ANALYSIS');
+  expect(onSelect).toHaveBeenCalledWith(NewCodePeriodSettingType.SPECIFIC_ANALYSIS);
 });
 
 function shallowRender(props: Partial<Props> = {}) {
index 95fcbe423d47cafaac24c97e5ea5d60e8c472014..a7aa306c6c67f73d14e44c896dc870a42453ed21 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BaselineSettingDays, { Props } from '../BaselineSettingDays';
 
 it('should render correctly', () => {
@@ -37,7 +38,7 @@ it('should callback when clicked', () => {
   const wrapper = shallowRender({ onSelect, selected: false });
 
   wrapper.find('RadioCard').first().simulate('click');
-  expect(onSelect).toHaveBeenCalledWith('NUMBER_OF_DAYS');
+  expect(onSelect).toHaveBeenCalledWith(NewCodePeriodSettingType.NUMBER_OF_DAYS);
 });
 
 it('should callback when changing days', () => {
index b680fb9cb884345b0719299729592ec0982a2ab3..9072f671c8dff726ef587bb5a08eec49e73fa5cc 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BaselineSettingPreviousVersion, { Props } from '../BaselineSettingPreviousVersion';
 
 it('should render correctly', () => {
@@ -31,7 +32,7 @@ it('should callback when clicked', () => {
   const wrapper = shallowRender({ onSelect, selected: false });
 
   wrapper.find('RadioCard').first().simulate('click');
-  expect(onSelect).toHaveBeenCalledWith('PREVIOUS_VERSION');
+  expect(onSelect).toHaveBeenCalledWith(NewCodePeriodSettingType.PREVIOUS_VERSION);
 });
 
 function shallowRender(props: Partial<Props> = {}) {
index c240643795de12721d9b8b95c7a353bec9a0c217..dd9f28b5a7698cce75815863f3e45fd49808a96e 100644 (file)
@@ -22,6 +22,7 @@ import * as React from 'react';
 import { OptionProps, Props as ReactSelectProps } from 'react-select';
 import RadioCard from '../../../../components/controls/RadioCard';
 import Select from '../../../../components/controls/Select';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BaselineSettingReferenceBranch, {
   BaselineSettingReferenceBranchProps,
   BranchOption,
@@ -49,7 +50,7 @@ it('should callback when clicked', () => {
   const wrapper = shallowRender({ onSelect, selected: false });
 
   wrapper.find(RadioCard).first().simulate('click');
-  expect(onSelect).toHaveBeenCalledWith('REFERENCE_BRANCH');
+  expect(onSelect).toHaveBeenCalledWith(NewCodePeriodSettingType.REFERENCE_BRANCH);
 });
 
 it('should callback when changing selection', () => {
index 06f71f16c33fb33377fa3508af146452c0f87ab5..8a0e83b8e503e80cb9d365c1c1da3760685cd4f8 100644 (file)
@@ -22,6 +22,7 @@ import * as React from 'react';
 import { setNewCodePeriod } from '../../../../api/newCodePeriod';
 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
 import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BranchBaselineSettingModal from '../BranchBaselineSettingModal';
 
 jest.mock('../../../../api/newCodePeriod', () => ({
@@ -38,7 +39,7 @@ it('should render correctly', () => {
 it('should display the branch analysis list when necessary', () => {
   const wrapper = shallowRender();
 
-  wrapper.setState({ selected: 'SPECIFIC_ANALYSIS' });
+  wrapper.setState({ selected: NewCodePeriodSettingType.SPECIFIC_ANALYSIS });
 
   expect(wrapper.find('BranchAnalysisList')).toHaveLength(1);
 });
@@ -51,7 +52,10 @@ it('should save correctly', async () => {
     component,
   });
 
-  wrapper.setState({ analysis: 'analysis572893', selected: 'SPECIFIC_ANALYSIS' });
+  wrapper.setState({
+    analysis: 'analysis572893',
+    selected: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
+  });
   await waitAndUpdate(wrapper);
 
   wrapper.instance().handleSubmit(mockEvent());
@@ -59,7 +63,7 @@ it('should save correctly', async () => {
 
   expect(setNewCodePeriod).toHaveBeenCalledWith({
     project: component,
-    type: 'SPECIFIC_ANALYSIS',
+    type: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
     value: 'analysis572893',
     branch: 'branchname',
   });
index df935426e904bab14b729b16301d672fdeabf0e9..3fab1ff976f087a5e9b70710bb73bc6133448b03 100644 (file)
@@ -23,6 +23,7 @@ import { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../../api/n
 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BranchBaselineSettingModal from '../BranchBaselineSettingModal';
 import BranchList from '../BranchList';
 
@@ -35,7 +36,7 @@ const newCodePeriods = [
   {
     projectKey: '',
     branchKey: 'master',
-    type: 'NUMBER_OF_DAYS',
+    type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
     value: '27',
   },
 ];
@@ -73,7 +74,9 @@ it('should toggle popup', async () => {
   expect(nodes).toHaveLength(1);
   expect(nodes.first().props().branch).toEqual(mockMainBranch());
 
-  wrapper.instance().closeEditModal('master', { type: 'NUMBER_OF_DAYS', value: '23' });
+  wrapper
+    .instance()
+    .closeEditModal('master', { type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '23' });
 
   expect(wrapper.find('BranchBaselineSettingModal')).toHaveLength(0);
   expect(wrapper.state().branches.find((b) => b.name === 'master')).toEqual({
@@ -82,7 +85,7 @@ it('should toggle popup', async () => {
     isMain: true,
     name: 'master',
     newCodePeriod: {
-      type: 'NUMBER_OF_DAYS',
+      type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
       value: '23',
     },
   });
@@ -93,7 +96,7 @@ function shallowRender(props: Partial<BranchList['props']> = {}) {
     <BranchList
       branchList={[]}
       component={mockComponent()}
-      inheritedSetting={{ type: 'PREVIOUS_VERSION' }}
+      inheritedSetting={{ type: NewCodePeriodSettingType.PREVIOUS_VERSION }}
       {...props}
     />
   );
index 66f6593bb165c8c636a9ce42af77154b8442e40d..12f2bb83be1ab6db2ffe464af8e24ba56881dced 100644 (file)
@@ -21,6 +21,7 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { ActionsDropdownItem } from '../../../../components/controls/ActionsDropdown';
 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import BranchListRow, { BranchListRowProps } from '../BranchListRow';
 
 it('should render correctly', () => {
@@ -28,17 +29,23 @@ it('should render correctly', () => {
   expect(
     shallowRender({
       branch: mockBranch({ name: 'branch-7.3' }),
-      inheritedSetting: { type: 'REFERENCE_BRANCH', value: 'branch-7.3' },
+      inheritedSetting: { type: NewCodePeriodSettingType.REFERENCE_BRANCH, value: 'branch-7.3' },
     })
   ).toMatchSnapshot('faulty branch');
   expect(
     shallowRender({
-      branch: { ...mockBranch(), newCodePeriod: { type: 'NUMBER_OF_DAYS', value: '21' } },
+      branch: {
+        ...mockBranch(),
+        newCodePeriod: { type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '21' },
+      },
     })
   ).toMatchSnapshot('branch with number of days');
   expect(
     shallowRender({
-      branch: { ...mockBranch(), newCodePeriod: { type: 'PREVIOUS_VERSION' } },
+      branch: {
+        ...mockBranch(),
+        newCodePeriod: { type: NewCodePeriodSettingType.PREVIOUS_VERSION },
+      },
     })
   ).toMatchSnapshot('branch with previous version');
   expect(
@@ -46,7 +53,7 @@ it('should render correctly', () => {
       branch: {
         ...mockBranch(),
         newCodePeriod: {
-          type: 'SPECIFIC_ANALYSIS',
+          type: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
           value: 'A85835',
           effectiveValue: '2018-12-02T13:01:12',
         },
@@ -55,7 +62,10 @@ it('should render correctly', () => {
   ).toMatchSnapshot('branch with specific analysis');
   expect(
     shallowRender({
-      branch: { ...mockBranch(), newCodePeriod: { type: 'REFERENCE_BRANCH', value: 'master' } },
+      branch: {
+        ...mockBranch(),
+        newCodePeriod: { type: NewCodePeriodSettingType.REFERENCE_BRANCH, value: 'master' },
+      },
     })
   ).toMatchSnapshot('branch with reference branch');
 });
@@ -74,7 +84,10 @@ it('should callback to reset when clicked', () => {
   const resetToDefault = jest.fn();
   const branchName = 'branch-6.5';
   const wrapper = shallowRender({
-    branch: { ...mockBranch({ name: branchName }), newCodePeriod: { type: 'REFERENCE_BRANCH' } },
+    branch: {
+      ...mockBranch({ name: branchName }),
+      newCodePeriod: { type: NewCodePeriodSettingType.REFERENCE_BRANCH },
+    },
     onResetToDefault: resetToDefault,
   });
 
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineApp-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineApp-test.tsx
new file mode 100644 (file)
index 0000000..1f368a4
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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,
+  resetNewCodePeriod,
+  setNewCodePeriod,
+} from '../../../../api/newCodePeriod';
+import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockAppState } from '../../../../helpers/testMocks';
+import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
+import { NewCodePeriodSettingType } from '../../../../types/types';
+import { ProjectBaselineApp } from '../ProjectBaselineApp';
+
+jest.mock('../../../../api/newCodePeriod', () => ({
+  getNewCodePeriod: jest.fn().mockResolvedValue({}),
+  resetNewCodePeriod: jest.fn().mockResolvedValue({}),
+  setNewCodePeriod: jest.fn().mockResolvedValue({}),
+}));
+
+it('should render correctly', async () => {
+  let wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot();
+
+  wrapper = shallowRender({ appState: mockAppState({ canAdmin: true }), hasFeature: () => false });
+  await waitAndUpdate(wrapper);
+  expect(wrapper).toMatchSnapshot('without branch support');
+});
+
+it('should initialize correctly', async () => {
+  const wrapper = shallowRender({
+    branchLikes: [mockBranch(), mockPullRequest(), mockMainBranch()],
+  });
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.state().branchList).toHaveLength(2);
+  expect(wrapper.state().referenceBranch).toBe('master');
+});
+
+it('should not display reset button if project setting is not set', () => {
+  const wrapper = shallowRender();
+
+  expect(wrapper.find('Button')).toHaveLength(0);
+});
+
+it('should reset the setting correctly', async () => {
+  const wrapper = shallowRender();
+  await waitAndUpdate(wrapper);
+  wrapper.instance().resetSetting();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state('currentSetting')).toBeUndefined();
+  expect(wrapper.state('selected')).toBeUndefined();
+});
+
+it('should save correctly', async () => {
+  const component = mockComponent();
+  const wrapper = shallowRender({ component });
+  await waitAndUpdate(wrapper);
+  wrapper.setState({ selected: NewCodePeriodSettingType.NUMBER_OF_DAYS, days: '23' });
+  wrapper.instance().handleSubmit(mockEvent());
+  await waitAndUpdate(wrapper);
+  expect(setNewCodePeriod).toHaveBeenCalledWith({
+    project: component.key,
+    type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
+    value: '23',
+  });
+  expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
+});
+
+it('should handle errors gracefully', async () => {
+  (getNewCodePeriod as jest.Mock).mockRejectedValue('error');
+  (setNewCodePeriod as jest.Mock).mockRejectedValue('error');
+  (resetNewCodePeriod as jest.Mock).mockRejectedValue('error');
+
+  const wrapper = shallowRender();
+  wrapper.setState({ selected: NewCodePeriodSettingType.PREVIOUS_VERSION });
+  await waitAndUpdate(wrapper);
+
+  expect(wrapper.state('loading')).toBe(false);
+  wrapper.instance().resetSetting();
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state('saving')).toBe(false);
+  wrapper.instance().handleSubmit(mockEvent());
+  await waitAndUpdate(wrapper);
+  expect(wrapper.state('saving')).toBe(false);
+});
+
+function shallowRender(props: Partial<ProjectBaselineApp['props']> = {}) {
+  return shallow<ProjectBaselineApp>(
+    <ProjectBaselineApp
+      branchLike={mockBranch()}
+      branchLikes={[mockMainBranch()]}
+      appState={mockAppState({ canAdmin: true })}
+      hasFeature={jest.fn().mockReturnValue(true)}
+      component={mockComponent()}
+      {...props}
+    />
+  );
+}
index 09b2ecdfe1da564c5d41a9717a831b0d3c11d40d..740f830be9cb05edda3a6a8a81b71ddce8c12f38 100644 (file)
@@ -20,6 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
+import { NewCodePeriodSettingType } from '../../../../types/types';
 import ProjectBaselineSelector, { ProjectBaselineSelectorProps } from '../ProjectBaselineSelector';
 
 it('should render correctly', () => {
@@ -27,18 +28,21 @@ it('should render correctly', () => {
   expect(
     shallowRender({
       branchesEnabled: false,
-      generalSetting: { type: 'NUMBER_OF_DAYS', value: '23' },
+      generalSetting: { type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '23' },
     })
   ).toMatchSnapshot();
   expect(
-    shallowRender({ branchesEnabled: false, generalSetting: { type: 'NUMBER_OF_DAYS', value: '' } })
+    shallowRender({
+      branchesEnabled: false,
+      generalSetting: { type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '' },
+    })
   ).toMatchSnapshot();
 });
 
 it('should not show save button when unchanged', () => {
   const wrapper = shallowRender({
-    currentSetting: 'PREVIOUS_VERSION',
-    selected: 'PREVIOUS_VERSION',
+    currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
+    selected: NewCodePeriodSettingType.PREVIOUS_VERSION,
     overrideGeneralSetting: true,
   });
   expect(wrapper.find('SubmitButton').parent().hasClass('invisible')).toBe(true);
@@ -46,8 +50,8 @@ it('should not show save button when unchanged', () => {
 
 it('should show save button when changed', () => {
   const wrapper = shallowRender({
-    currentSetting: 'PREVIOUS_VERSION',
-    selected: 'NUMBER_OF_DAYS',
+    currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
+    selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
     overrideGeneralSetting: true,
   });
   expect(wrapper.find('SubmitButton')).toHaveLength(1);
@@ -55,10 +59,10 @@ it('should show save button when changed', () => {
 
 it('should show save button when value changed', () => {
   const wrapper = shallowRender({
-    currentSetting: 'NUMBER_OF_DAYS',
+    currentSetting: NewCodePeriodSettingType.NUMBER_OF_DAYS,
     currentSettingValue: '23',
     days: '25',
-    selected: 'NUMBER_OF_DAYS',
+    selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
     overrideGeneralSetting: true,
   });
   expect(wrapper.find('SubmitButton')).toHaveLength(1);
@@ -66,10 +70,10 @@ it('should show save button when value changed', () => {
 
 it('should disable the save button when saving', () => {
   const wrapper = shallowRender({
-    currentSetting: 'NUMBER_OF_DAYS',
+    currentSetting: NewCodePeriodSettingType.NUMBER_OF_DAYS,
     currentSettingValue: '25',
     saving: true,
-    selected: 'PREVIOUS_VERSION',
+    selected: NewCodePeriodSettingType.PREVIOUS_VERSION,
     overrideGeneralSetting: true,
   });
 
@@ -78,9 +82,9 @@ it('should disable the save button when saving', () => {
 
 it('should disable the save button when date is invalid', () => {
   const wrapper = shallowRender({
-    currentSetting: 'PREVIOUS_VERSION',
+    currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
     days: 'hello',
-    selected: 'NUMBER_OF_DAYS',
+    selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
     overrideGeneralSetting: true,
   });
 
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap
deleted file mode 100644 (file)
index 847b62f..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
-  <Suggestions
-    suggestions="project_baseline"
-  />
-  <div
-    className="page page-limited"
-  >
-    <AppHeader
-      canAdmin={true}
-    />
-    <div
-      className="panel-white project-baseline"
-    >
-      <h2>
-        project_baseline.default_setting
-      </h2>
-      <ProjectBaselineSelector
-        analysis=""
-        branch={
-          {
-            "analysisDate": "2018-01-01",
-            "excludedFromPurge": true,
-            "isMain": false,
-            "name": "branch-6.7",
-          }
-        }
-        branchList={
-          [
-            {
-              "analysisDate": "2018-01-01",
-              "excludedFromPurge": true,
-              "isMain": true,
-              "name": "master",
-            },
-          ]
-        }
-        branchesEnabled={true}
-        component="my-project"
-        currentSetting="PREVIOUS_VERSION"
-        days="30"
-        generalSetting={
-          {
-            "type": "PREVIOUS_VERSION",
-          }
-        }
-        onCancel={[Function]}
-        onSelectAnalysis={[Function]}
-        onSelectDays={[Function]}
-        onSelectReferenceBranch={[Function]}
-        onSelectSetting={[Function]}
-        onSubmit={[Function]}
-        onToggleSpecificSetting={[Function]}
-        overrideGeneralSetting={true}
-        referenceBranch="master"
-        saving={false}
-        selected="PREVIOUS_VERSION"
-      />
-      <div
-        className="spacer-top invisible"
-      >
-        <span
-          className="text-success"
-        >
-          <AlertSuccessIcon
-            className="spacer-right"
-          />
-          settings.state.saved
-        </span>
-      </div>
-      <div
-        className="huge-spacer-top branch-baseline-selector"
-      >
-        <hr />
-        <h2>
-          project_baseline.configure_branches
-        </h2>
-        <BranchList
-          branchList={
-            [
-              {
-                "analysisDate": "2018-01-01",
-                "excludedFromPurge": true,
-                "isMain": true,
-                "name": "master",
-              },
-            ]
-          }
-          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": [],
-            }
-          }
-          inheritedSetting={
-            {
-              "type": "PREVIOUS_VERSION",
-              "value": undefined,
-            }
-          }
-        />
-      </div>
-    </div>
-  </div>
-</Fragment>
-`;
-
-exports[`should render correctly: without branch support 1`] = `
-<Fragment>
-  <Suggestions
-    suggestions="project_baseline"
-  />
-  <div
-    className="page page-limited"
-  >
-    <AppHeader
-      canAdmin={true}
-    />
-    <div
-      className="panel-white project-baseline"
-    >
-      <ProjectBaselineSelector
-        analysis=""
-        branch={
-          {
-            "analysisDate": "2018-01-01",
-            "excludedFromPurge": true,
-            "isMain": false,
-            "name": "branch-6.7",
-          }
-        }
-        branchList={
-          [
-            {
-              "analysisDate": "2018-01-01",
-              "excludedFromPurge": true,
-              "isMain": true,
-              "name": "master",
-            },
-          ]
-        }
-        branchesEnabled={false}
-        component="my-project"
-        currentSetting="PREVIOUS_VERSION"
-        days="30"
-        generalSetting={
-          {
-            "type": "PREVIOUS_VERSION",
-          }
-        }
-        onCancel={[Function]}
-        onSelectAnalysis={[Function]}
-        onSelectDays={[Function]}
-        onSelectReferenceBranch={[Function]}
-        onSelectSetting={[Function]}
-        onSubmit={[Function]}
-        onToggleSpecificSetting={[Function]}
-        overrideGeneralSetting={true}
-        referenceBranch="master"
-        saving={false}
-        selected="PREVIOUS_VERSION"
-      />
-      <div
-        className="spacer-top invisible"
-      >
-        <span
-          className="text-success"
-        >
-          <AlertSuccessIcon
-            className="spacer-right"
-          />
-          settings.state.saved
-        </span>
-      </div>
-    </div>
-  </div>
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/ProjectBaselineApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/ProjectBaselineApp-test.tsx.snap
new file mode 100644 (file)
index 0000000..847b62f
--- /dev/null
@@ -0,0 +1,196 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+  <Suggestions
+    suggestions="project_baseline"
+  />
+  <div
+    className="page page-limited"
+  >
+    <AppHeader
+      canAdmin={true}
+    />
+    <div
+      className="panel-white project-baseline"
+    >
+      <h2>
+        project_baseline.default_setting
+      </h2>
+      <ProjectBaselineSelector
+        analysis=""
+        branch={
+          {
+            "analysisDate": "2018-01-01",
+            "excludedFromPurge": true,
+            "isMain": false,
+            "name": "branch-6.7",
+          }
+        }
+        branchList={
+          [
+            {
+              "analysisDate": "2018-01-01",
+              "excludedFromPurge": true,
+              "isMain": true,
+              "name": "master",
+            },
+          ]
+        }
+        branchesEnabled={true}
+        component="my-project"
+        currentSetting="PREVIOUS_VERSION"
+        days="30"
+        generalSetting={
+          {
+            "type": "PREVIOUS_VERSION",
+          }
+        }
+        onCancel={[Function]}
+        onSelectAnalysis={[Function]}
+        onSelectDays={[Function]}
+        onSelectReferenceBranch={[Function]}
+        onSelectSetting={[Function]}
+        onSubmit={[Function]}
+        onToggleSpecificSetting={[Function]}
+        overrideGeneralSetting={true}
+        referenceBranch="master"
+        saving={false}
+        selected="PREVIOUS_VERSION"
+      />
+      <div
+        className="spacer-top invisible"
+      >
+        <span
+          className="text-success"
+        >
+          <AlertSuccessIcon
+            className="spacer-right"
+          />
+          settings.state.saved
+        </span>
+      </div>
+      <div
+        className="huge-spacer-top branch-baseline-selector"
+      >
+        <hr />
+        <h2>
+          project_baseline.configure_branches
+        </h2>
+        <BranchList
+          branchList={
+            [
+              {
+                "analysisDate": "2018-01-01",
+                "excludedFromPurge": true,
+                "isMain": true,
+                "name": "master",
+              },
+            ]
+          }
+          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": [],
+            }
+          }
+          inheritedSetting={
+            {
+              "type": "PREVIOUS_VERSION",
+              "value": undefined,
+            }
+          }
+        />
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
+
+exports[`should render correctly: without branch support 1`] = `
+<Fragment>
+  <Suggestions
+    suggestions="project_baseline"
+  />
+  <div
+    className="page page-limited"
+  >
+    <AppHeader
+      canAdmin={true}
+    />
+    <div
+      className="panel-white project-baseline"
+    >
+      <ProjectBaselineSelector
+        analysis=""
+        branch={
+          {
+            "analysisDate": "2018-01-01",
+            "excludedFromPurge": true,
+            "isMain": false,
+            "name": "branch-6.7",
+          }
+        }
+        branchList={
+          [
+            {
+              "analysisDate": "2018-01-01",
+              "excludedFromPurge": true,
+              "isMain": true,
+              "name": "master",
+            },
+          ]
+        }
+        branchesEnabled={false}
+        component="my-project"
+        currentSetting="PREVIOUS_VERSION"
+        days="30"
+        generalSetting={
+          {
+            "type": "PREVIOUS_VERSION",
+          }
+        }
+        onCancel={[Function]}
+        onSelectAnalysis={[Function]}
+        onSelectDays={[Function]}
+        onSelectReferenceBranch={[Function]}
+        onSelectSetting={[Function]}
+        onSubmit={[Function]}
+        onToggleSpecificSetting={[Function]}
+        overrideGeneralSetting={true}
+        referenceBranch="master"
+        saving={false}
+        selected="PREVIOUS_VERSION"
+      />
+      <div
+        className="spacer-top invisible"
+      >
+        <span
+          className="text-success"
+        >
+          <AlertSuccessIcon
+            className="spacer-right"
+          />
+          settings.state.saved
+        </span>
+      </div>
+    </div>
+  </div>
+</Fragment>
+`;
index 044e63b9da6f318bc4b84d843f5e9c1957896c44..2d53227ef9fde150b5b65930907f4e3e40840157 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 { NewCodePeriodSettingType } from '../../../../types/types';
 import { getSettingValue, validateSetting } from '../../utils';
 
 describe('getSettingValue', () => {
@@ -27,19 +28,27 @@ describe('getSettingValue', () => {
   };
 
   it('should work for Days', () => {
-    expect(getSettingValue({ ...state, type: 'NUMBER_OF_DAYS' })).toBe(state.days);
+    expect(getSettingValue({ ...state, type: NewCodePeriodSettingType.NUMBER_OF_DAYS })).toBe(
+      state.days
+    );
   });
 
   it('should work for Analysis', () => {
-    expect(getSettingValue({ ...state, type: 'SPECIFIC_ANALYSIS' })).toBe(state.analysis);
+    expect(getSettingValue({ ...state, type: NewCodePeriodSettingType.SPECIFIC_ANALYSIS })).toBe(
+      state.analysis
+    );
   });
 
   it('should work for Previous version', () => {
-    expect(getSettingValue({ ...state, type: 'PREVIOUS_VERSION' })).toBeUndefined();
+    expect(
+      getSettingValue({ ...state, type: NewCodePeriodSettingType.PREVIOUS_VERSION })
+    ).toBeUndefined();
   });
 
   it('should work for Reference branch', () => {
-    expect(getSettingValue({ ...state, type: 'REFERENCE_BRANCH' })).toBe(state.referenceBranch);
+    expect(getSettingValue({ ...state, type: NewCodePeriodSettingType.REFERENCE_BRANCH })).toBe(
+      state.referenceBranch
+    );
   });
 });
 
@@ -48,68 +57,68 @@ describe('validateSettings', () => {
     expect(validateSetting({ days: '' })).toEqual({ isChanged: false, isValid: false });
     expect(
       validateSetting({
-        currentSetting: 'PREVIOUS_VERSION',
+        currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
         days: '12',
-        selected: 'NUMBER_OF_DAYS',
+        selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
       })
     ).toEqual({ isChanged: true, isValid: true });
     expect(
       validateSetting({
-        currentSetting: 'PREVIOUS_VERSION',
+        currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
         days: 'nope',
-        selected: 'NUMBER_OF_DAYS',
+        selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
       })
     ).toEqual({ isChanged: true, isValid: false });
     expect(
       validateSetting({
-        currentSetting: 'NUMBER_OF_DAYS',
+        currentSetting: NewCodePeriodSettingType.NUMBER_OF_DAYS,
         currentSettingValue: '15',
         days: '15',
-        selected: 'NUMBER_OF_DAYS',
+        selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
       })
     ).toEqual({ isChanged: false, isValid: true });
     expect(
       validateSetting({
-        currentSetting: 'NUMBER_OF_DAYS',
+        currentSetting: NewCodePeriodSettingType.NUMBER_OF_DAYS,
         currentSettingValue: '15',
         days: '13',
-        selected: 'NUMBER_OF_DAYS',
+        selected: NewCodePeriodSettingType.NUMBER_OF_DAYS,
       })
     ).toEqual({ isChanged: true, isValid: true });
     expect(
       validateSetting({
         analysis: 'analysis1',
-        currentSetting: 'SPECIFIC_ANALYSIS',
+        currentSetting: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
         currentSettingValue: 'analysis1',
         days: '',
-        selected: 'SPECIFIC_ANALYSIS',
+        selected: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
       })
     ).toEqual({ isChanged: false, isValid: true });
     expect(
       validateSetting({
         analysis: 'analysis2',
-        currentSetting: 'SPECIFIC_ANALYSIS',
+        currentSetting: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
         currentSettingValue: 'analysis1',
         days: '',
-        selected: 'SPECIFIC_ANALYSIS',
+        selected: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
       })
     ).toEqual({ isChanged: true, isValid: true });
     expect(
       validateSetting({
-        currentSetting: 'REFERENCE_BRANCH',
+        currentSetting: NewCodePeriodSettingType.REFERENCE_BRANCH,
         currentSettingValue: 'master',
         days: '',
         referenceBranch: 'master',
-        selected: 'REFERENCE_BRANCH',
+        selected: NewCodePeriodSettingType.REFERENCE_BRANCH,
       })
     ).toEqual({ isChanged: false, isValid: true });
     expect(
       validateSetting({
-        currentSetting: 'REFERENCE_BRANCH',
+        currentSetting: NewCodePeriodSettingType.REFERENCE_BRANCH,
         currentSettingValue: 'master',
         days: '',
         referenceBranch: '',
-        selected: 'REFERENCE_BRANCH',
+        selected: NewCodePeriodSettingType.REFERENCE_BRANCH,
       })
     ).toEqual({ isChanged: true, isValid: false });
   });
@@ -125,7 +134,7 @@ describe('validateSettings', () => {
     });
     expect(
       validateSetting({
-        currentSetting: 'PREVIOUS_VERSION',
+        currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
         days: '',
         overrideGeneralSetting: false,
       })
diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/constants.ts b/server/sonar-web/src/main/js/apps/projectBaseline/constants.ts
new file mode 100644 (file)
index 0000000..00c2c82
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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 { NewCodePeriodSettingType } from '../../types/types';
+
+export const DEFAULT_GENERAL_SETTING_TYPE: NewCodePeriodSettingType =
+  NewCodePeriodSettingType.PREVIOUS_VERSION;
index 5397eae95531bddd7e94660b4c2dc95e084f4b12..2235fe7aabf0009d1381877319de4325df81d794 100644 (file)
@@ -19,8 +19,8 @@
  */
 import React from 'react';
 import { Route } from 'react-router-dom';
-import App from './components/App';
+import ProjectBaselineApp from './components/ProjectBaselineApp';
 
-const routes = () => <Route path="baseline" element={<App />} />;
+const routes = () => <Route path="baseline" element={<ProjectBaselineApp />} />;
 
 export default routes;
index 3f249e2fd771e635f555d1db4a8c79dd891b0fe6..2ca20b85b1e9fba26dabc710953e3ab4335ad920 100644 (file)
@@ -37,11 +37,11 @@ export function getSettingValue({
   type?: NewCodePeriodSettingType;
 }) {
   switch (type) {
-    case 'NUMBER_OF_DAYS':
+    case NewCodePeriodSettingType.NUMBER_OF_DAYS:
       return days;
-    case 'REFERENCE_BRANCH':
+    case NewCodePeriodSettingType.REFERENCE_BRANCH:
       return referenceBranch;
-    case 'SPECIFIC_ANALYSIS':
+    case NewCodePeriodSettingType.SPECIFIC_ANALYSIS:
       return analysis;
     default:
       return undefined;
@@ -74,17 +74,19 @@ export function validateSetting(state: {
     isChanged =
       overrideGeneralSetting === false ||
       selected !== currentSetting ||
-      (selected === 'NUMBER_OF_DAYS' && days !== currentSettingValue) ||
-      (selected === 'SPECIFIC_ANALYSIS' && analysis !== currentSettingValue) ||
-      (selected === 'REFERENCE_BRANCH' && referenceBranch !== currentSettingValue);
+      (selected === NewCodePeriodSettingType.NUMBER_OF_DAYS && days !== currentSettingValue) ||
+      (selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS &&
+        analysis !== currentSettingValue) ||
+      (selected === NewCodePeriodSettingType.REFERENCE_BRANCH &&
+        referenceBranch !== currentSettingValue);
   }
 
   const isValid =
     overrideGeneralSetting === false ||
-    selected === 'PREVIOUS_VERSION' ||
-    (selected === 'SPECIFIC_ANALYSIS' && analysis.length > 0) ||
-    (selected === 'NUMBER_OF_DAYS' && validateDays(days)) ||
-    (selected === 'REFERENCE_BRANCH' && referenceBranch.length > 0);
+    selected === NewCodePeriodSettingType.PREVIOUS_VERSION ||
+    (selected === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && analysis.length > 0) ||
+    (selected === NewCodePeriodSettingType.NUMBER_OF_DAYS && validateDays(days)) ||
+    (selected === NewCodePeriodSettingType.REFERENCE_BRANCH && referenceBranch.length > 0);
 
   return { isChanged, isValid };
 }
index c0167864efc4044b30596baecccffed187e41b69..ca7d3221869dabfa5bb0aa97f47664d340908d7a 100644 (file)
@@ -90,10 +90,9 @@ export default class Definition extends React.PureComponent<Props, State> {
         settingValue,
       });
 
-      this.timeout = window.setTimeout(
-        () => this.setState({ success: false }),
-        SAFE_SET_STATE_DELAY
-      );
+      this.timeout = window.setTimeout(() => {
+        this.setState({ success: false });
+      }, SAFE_SET_STATE_DELAY);
     } catch (e) {
       const validationMessage = await parseError(e as Response);
       this.setState({ loading: false, validationMessage });
@@ -179,10 +178,9 @@ export default class Definition extends React.PureComponent<Props, State> {
           settingValue,
         });
 
-        this.timeout = window.setTimeout(
-          () => this.setState({ success: false }),
-          SAFE_SET_STATE_DELAY
-        );
+        this.timeout = window.setTimeout(() => {
+          this.setState({ success: false });
+        }, SAFE_SET_STATE_DELAY);
       } catch (e) {
         const validationMessage = await parseError(e as Response);
         this.setState({ loading: false, validationMessage });
index d6c260352c6b26c437e7dd51dfcc3ff0a96c9ea3..d59c3bf877546596a3e860aac70386ad46e15095 100644 (file)
@@ -40,8 +40,6 @@ interface State {
   success: boolean;
 }
 
-const DEFAULT_SETTING = 'PREVIOUS_VERSION';
-
 export default class NewCodePeriod extends React.PureComponent<{}, State> {
   mounted = false;
   state: State = {
@@ -63,14 +61,12 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> {
   fetchNewCodePeriodSetting() {
     getNewCodePeriod()
       .then(({ type, value }) => {
-        const currentSetting = type || DEFAULT_SETTING;
-
         this.setState(({ days }) => ({
-          currentSetting,
-          days: currentSetting === 'NUMBER_OF_DAYS' ? String(value) : days,
+          currentSetting: type,
+          days: type === NewCodePeriodSettingType.NUMBER_OF_DAYS ? String(value) : days,
           loading: false,
           currentSettingValue: value,
-          selected: currentSetting,
+          selected: type,
         }));
       })
       .catch(() => {
@@ -89,7 +85,10 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> {
   onCancel = () => {
     this.setState(({ currentSetting, currentSettingValue, days }) => ({
       selected: currentSetting,
-      days: currentSetting === 'NUMBER_OF_DAYS' ? String(currentSettingValue) : days,
+      days:
+        currentSetting === NewCodePeriodSettingType.NUMBER_OF_DAYS
+          ? String(currentSettingValue)
+          : days,
     }));
   };
 
@@ -99,29 +98,27 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> {
     const { days, selected } = this.state;
 
     const type = selected;
-    const value = type === 'NUMBER_OF_DAYS' ? days : undefined;
-
-    if (type) {
-      this.setState({ saving: true, success: false });
-      setNewCodePeriod({
-        type,
-        value,
-      }).then(
-        () => {
-          this.setState({
-            saving: false,
-            currentSetting: type,
-            currentSettingValue: value || undefined,
-            success: true,
-          });
-        },
-        () => {
-          this.setState({
-            saving: false,
-          });
-        }
-      );
-    }
+    const value = type === NewCodePeriodSettingType.NUMBER_OF_DAYS ? days : undefined;
+
+    this.setState({ saving: true, success: false });
+    setNewCodePeriod({
+      type: type as NewCodePeriodSettingType,
+      value,
+    }).then(
+      () => {
+        this.setState({
+          saving: false,
+          currentSetting: type,
+          currentSettingValue: value || undefined,
+          success: true,
+        });
+      },
+      () => {
+        this.setState({
+          saving: false,
+        });
+      }
+    );
   };
 
   render() {
@@ -130,9 +127,10 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> {
 
     const isChanged =
       selected !== currentSetting ||
-      (selected === 'NUMBER_OF_DAYS' && String(days) !== currentSettingValue);
+      (selected === NewCodePeriodSettingType.NUMBER_OF_DAYS &&
+        String(days) !== currentSettingValue);
 
-    const isValid = selected !== 'NUMBER_OF_DAYS' || validateDays(days);
+    const isValid = selected !== NewCodePeriodSettingType.NUMBER_OF_DAYS || validateDays(days);
 
     return (
       <ul className="settings-sub-categories-list">
@@ -174,7 +172,7 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> {
                       <BaselineSettingPreviousVersion
                         isDefault={true}
                         onSelect={this.onSelectSetting}
-                        selected={selected === 'PREVIOUS_VERSION'}
+                        selected={selected === NewCodePeriodSettingType.PREVIOUS_VERSION}
                       />
                       <BaselineSettingDays
                         className="spacer-top"
@@ -183,7 +181,7 @@ export default class NewCodePeriod extends React.PureComponent<{}, State> {
                         isValid={isValid}
                         onChangeDays={this.onSelectDays}
                         onSelect={this.onSelectSetting}
-                        selected={selected === 'NUMBER_OF_DAYS'}
+                        selected={selected === NewCodePeriodSettingType.NUMBER_OF_DAYS}
                       />
                       {isChanged && (
                         <div className="big-spacer-top">
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
new file mode 100644 (file)
index 0000000..a6df600
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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 { uniq } from 'lodash';
+import * as React from 'react';
+import { byRole, byText } from 'testing-library-selector';
+import { DEFAULT_DEFINITIONS_MOCK } from '../../../../api/mocks/SettingsServiceMock';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { AdditionalCategoryComponentProps, ADDITIONAL_CATEGORIES } from '../AdditionalCategories';
+
+const ui = {
+  introduction: byText('settings.analysis_scope.wildcards.introduction'),
+  docLink: byRole('link', { name: /learn_more/ }),
+};
+
+it('renders correctly', async () => {
+  renderAnalysisScope();
+
+  expect(await ui.introduction.find()).toBeInTheDocument();
+  expect(ui.docLink.get()).toBeInTheDocument();
+});
+
+function renderAnalysisScope(overrides: Partial<AdditionalCategoryComponentProps> = {}) {
+  const props = {
+    definitions: DEFAULT_DEFINITIONS_MOCK,
+    categories: uniq(DEFAULT_DEFINITIONS_MOCK.map((d) => d.category)),
+    selectedCategory: 'general',
+    component: mockComponent(),
+    ...overrides,
+  };
+  return renderComponent(<>{ADDITIONAL_CATEGORIES[2].renderComponent(props)}</>);
+}
index 40869c50470498e01c45f329ce1fb486ef4d4589..98d85ea341e48424a15fa9e6bb65c38f59cd159b 100644 (file)
  * 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { last } from 'lodash';
+import React from 'react';
+import selectEvent from 'react-select-event';
+import { byLabelText, byRole, byText } from 'testing-library-selector';
+import SettingsServiceMock, {
+  DEFAULT_DEFINITIONS_MOCK,
+} from '../../../../api/mocks/SettingsServiceMock';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockDefinition } from '../../../../helpers/mocks/settings';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { ExtendedSettingDefinition, SettingType, SettingValue } from '../../../../types/settings';
+import { Component } from '../../../../types/types';
 import Definition from '../Definition';
 
-jest.mock('../../../../api/settings', () => ({
-  getValue: jest.fn().mockResolvedValue({}),
-  resetSettingValue: jest.fn().mockResolvedValue(undefined),
-  setSettingValue: jest.fn().mockResolvedValue(undefined),
-}));
+jest.mock('../../../../api/settings');
+
+let settingsMock: SettingsServiceMock;
 
 beforeAll(() => {
-  jest.useFakeTimers();
+  settingsMock = new SettingsServiceMock();
 });
 
-afterAll(() => {
-  jest.runOnlyPendingTimers();
-  jest.useRealTimers();
+afterEach(() => {
+  settingsMock.reset();
 });
 
-beforeEach(() => {
-  jest.clearAllMocks();
+beforeEach(jest.clearAllMocks);
+
+const ui = {
+  nameHeading: (name: string) => byRole('heading', { name }),
+  announcementInput: byLabelText('property.sonar.announcement.message.name'),
+  securedInput: byRole('textbox', { name: 'property.sonar.announcement.message.secured.name' }),
+  multiValuesInput: byRole('textbox', { name: 'property.sonar.javascript.globals.name' }),
+  urlKindInput: byRole('textbox', { name: /sonar.auth.gitlab.url/ }),
+  fieldsInput: (name: string) => byRole('textbox', { name: `property.${name}.name` }),
+  savedMsg: byText('settings.state.saved'),
+  validationMsg: byText(/settings.state.validation_failed/),
+  jsonFormatStatus: byRole('status', { name: 'alert.tooltip.info' }),
+  jsonFormatButton: byRole('button', { name: 'settings.json.format' }),
+  toggleButton: byRole('switch'),
+  selectOption: (value: string) => byText(value),
+  saveButton: byRole('button', { name: 'save' }),
+  cancelButton: byRole('button', { name: 'cancel' }),
+  changeButton: byRole('button', { name: 'change_verb' }),
+  resetButton: (name: string | RegExp = 'reset_verb') => byRole('button', { name }),
+  deleteValueButton: byRole('button', {
+    name: /settings.definition.delete_value/,
+  }),
+  deleteFieldsButton: byRole('button', {
+    name: /settings.definitions.delete_fields/,
+  }),
+};
+
+it.each([
+  SettingType.TEXT,
+  SettingType.STRING,
+  SettingType.PASSWORD,
+  SettingType.INTEGER,
+  SettingType.LONG,
+  SettingType.FLOAT,
+  'uknown type',
+])(
+  'renders definition for SettingType = %s and can do operations',
+  async (settingType: SettingType) => {
+    const user = userEvent.setup();
+    renderDefinition({ type: settingType });
+
+    expect(
+      await ui.nameHeading('property.sonar.announcement.message.name').find()
+    ).toBeInTheDocument();
+
+    // Should see no empty validation message
+    await user.type(ui.announcementInput.get(), ' ');
+    await user.click(ui.saveButton.get());
+    expect(await ui.validationMsg.find()).toBeInTheDocument();
+
+    // Should save variable
+    await user.type(ui.announcementInput.get(), 'Testing');
+    await user.click(await ui.saveButton.find());
+    expect(ui.validationMsg.query()).not.toBeInTheDocument();
+    expect(ui.announcementInput.get()).toHaveValue(' Testing');
+    expect(ui.savedMsg.get()).toBeInTheDocument();
+
+    // Validation message when clearing input to empty
+    await user.clear(ui.announcementInput.get());
+    expect(ui.validationMsg.get()).toBeInTheDocument();
+
+    // Should reset to previous state on clicking cancel
+    await user.type(ui.announcementInput.get(), 'Testing2');
+    await user.click(ui.cancelButton.get());
+    expect(ui.announcementInput.get()).toHaveValue(' Testing');
+
+    // Clicking reset opens dialog and reset to default on confirm
+    await user.click(
+      ui.resetButton('settings.definition.reset.property.sonar.announcement.message.name').get()
+    );
+    await user.click(ui.resetButton().get());
+    expect(ui.announcementInput.get()).toHaveValue('');
+  }
+);
+
+it('renders definition for SettingType = JSON and can do operations', async () => {
+  const user = userEvent.setup();
+  renderDefinition({ type: SettingType.JSON });
+
+  expect(
+    await ui.nameHeading('property.sonar.announcement.message.name').find()
+  ).toBeInTheDocument();
+
+  // Should show error message if JSON format is not valid
+  await user.type(ui.announcementInput.get(), 'invalid format');
+  expect(ui.validationMsg.get()).toBeInTheDocument();
+  await user.click(ui.jsonFormatButton.get());
+  expect(ui.jsonFormatStatus.get()).toBeInTheDocument();
+
+  // Can save valid json and format it
+  await user.clear(ui.announcementInput.get());
+  await user.type(ui.announcementInput.get(), '1');
+  await user.click(ui.jsonFormatButton.get());
+  expect(ui.jsonFormatStatus.query()).not.toBeInTheDocument();
+
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
 });
 
-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('renders definition for SettingType = BOOLEAN and can do operations', async () => {
+  const user = userEvent.setup();
+  renderDefinition({
+    type: SettingType.BOOLEAN,
+  });
+
+  expect(
+    await ui.nameHeading('property.sonar.announcement.message.name').find()
+  ).toBeInTheDocument();
+
+  // Can toggle
+  await user.click(ui.toggleButton.get());
+  expect(ui.toggleButton.get()).toBeChecked();
+
+  // Can cancel toggle
+  await user.click(ui.cancelButton.get());
+  expect(ui.toggleButton.get()).not.toBeChecked();
+
+  // Can save toggle
+  await user.click(ui.toggleButton.get());
+  await user.click(ui.saveButton.get());
+  expect(ui.toggleButton.get()).toBeChecked();
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+
+  // Can reset toggle
+  await user.click(
+    ui.resetButton('settings.definition.reset.property.sonar.announcement.message.name').get()
   );
+  await user.click(ui.resetButton().get());
+  expect(ui.toggleButton.get()).not.toBeChecked();
 });
 
-it('should handle cancel', () => {
-  const wrapper = shallowRender();
-  wrapper.setState({ changedValue: 'whatever', validationMessage: 'something wrong' });
+it('renders definition for SettingType = SINGLE_SELECT_LIST and can do operations', async () => {
+  const user = userEvent.setup();
+  renderDefinition({
+    type: SettingType.SINGLE_SELECT_LIST,
+    options: ['first', 'second'],
+  });
 
-  wrapper.instance().handleCancel();
+  expect(
+    await ui.nameHeading('property.sonar.announcement.message.name').find()
+  ).toBeInTheDocument();
 
-  expect(wrapper.state().changedValue).toBeUndefined();
-  expect(wrapper.state().validationMessage).toBeUndefined();
-});
+  // Can select option
+  expect(ui.selectOption('Select...').get()).toBeInTheDocument();
+  await selectEvent.select(ui.announcementInput.get(), 'first');
+  expect(ui.selectOption('first').get()).toBeInTheDocument();
 
-describe('handleSave', () => {
-  it('should ignore when value unchanged', () => {
-    const wrapper = shallowRender();
+  // Can cancel action
+  await user.click(ui.cancelButton.get());
+  expect(ui.selectOption('Select...').get()).toBeInTheDocument();
 
-    wrapper.instance().handleSave();
+  // Can save
+  await selectEvent.select(ui.announcementInput.get(), 'second');
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+
+  // Can reset
+  await user.click(
+    ui.resetButton('settings.definition.reset.property.sonar.announcement.message.name').get()
+  );
+  await user.click(ui.resetButton().get());
+  expect(ui.selectOption('Select...').get()).toBeInTheDocument();
+});
 
-    expect(wrapper.state().loading).toBe(false);
-    expect(setSettingValue).not.toHaveBeenCalled();
+it('renders definition for SettingType = FORMATTED_TEXT and can do operations', async () => {
+  const user = userEvent.setup();
+  renderDefinition({
+    type: SettingType.FORMATTED_TEXT,
   });
 
-  it('should handle an empty value', () => {
-    const wrapper = shallowRender();
+  expect(
+    await ui.nameHeading('property.sonar.announcement.message.name').find()
+  ).toBeInTheDocument();
 
-    wrapper.setState({ changedValue: '' });
+  // Should see no empty validation message
+  await user.type(ui.announcementInput.get(), ' ');
+  await user.click(ui.saveButton.get());
+  expect(await ui.validationMsg.find()).toBeInTheDocument();
 
-    wrapper.instance().handleSave();
+  // Can cancel message
+  await user.clear(ui.announcementInput.get());
+  await user.type(ui.announcementInput.get(), 'msg');
+  await user.click(ui.cancelButton.get());
+  expect(ui.announcementInput.get()).toHaveValue('');
 
-    expect(wrapper.state().loading).toBe(false);
-    expect(wrapper.state().validationMessage).toBe('settings.state.value_cant_be_empty');
-    expect(setSettingValue).not.toHaveBeenCalled();
-  });
+  // Can save formatted message
+  await user.type(ui.announcementInput.get(), 'https://ok.com');
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+  expect(ui.announcementInput.query()).not.toBeInTheDocument();
+});
 
-  it('should save and update setting value', async () => {
-    const settingValue = mockSettingValue();
-    (getValue as jest.Mock).mockResolvedValueOnce(settingValue);
-    const definition = mockDefinition();
-    const wrapper = shallowRender({ definition });
+it('renders definition for multiValues type and can do operations', async () => {
+  const user = userEvent.setup();
+  renderDefinition(
+    DEFAULT_DEFINITIONS_MOCK[2],
+    {
+      key: DEFAULT_DEFINITIONS_MOCK[2].key,
+      values: DEFAULT_DEFINITIONS_MOCK[2].defaultValue?.split(','),
+    },
+    mockComponent()
+  );
 
-    wrapper.setState({ changedValue: 'new value' });
+  expect(await ui.nameHeading('property.sonar.javascript.globals.name').find()).toBeInTheDocument();
+  expect(ui.multiValuesInput.getAll()).toHaveLength(4);
 
-    wrapper.instance().handleSave();
+  // Should show validation message if no values
+  await user.click(ui.deleteValueButton.getAll()[0]);
+  await user.click(ui.deleteValueButton.getAll()[0]);
+  await user.click(ui.deleteValueButton.getAll()[0]);
 
-    expect(wrapper.state().loading).toBe(true);
+  expect(await ui.multiValuesInput.findAll()).toHaveLength(1);
+  expect(ui.validationMsg.get()).toBeInTheDocument();
 
-    await waitAndUpdate(wrapper);
+  // Can cancel and return to previous
+  await user.click(ui.cancelButton.get());
+  expect(ui.multiValuesInput.getAll()).toHaveLength(4);
 
-    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);
+  // Can update values and save
+  await user.type(last(ui.multiValuesInput.getAll()) as HTMLElement, 'new value');
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+  expect(ui.multiValuesInput.getAll()).toHaveLength(5);
 
-    jest.runAllTimers();
-    expect(wrapper.state().success).toBe(false);
-  });
+  // Can reset to default
+  await user.click(
+    ui.resetButton('settings.definition.reset.property.sonar.javascript.globals.name').get()
+  );
+  await user.click(ui.resetButton().get());
+  expect(ui.multiValuesInput.getAll()).toHaveLength(4);
 });
 
-it('should reset and update setting value', async () => {
-  const settingValue = mockSettingValue();
-  (getValue as jest.Mock).mockResolvedValueOnce(settingValue);
-  const definition = mockDefinition();
-  const wrapper = shallowRender({ definition });
+it('renders definition for SettingType = PROPERTY_SET and can do operations', async () => {
+  const user = userEvent.setup();
+  renderDefinition(DEFAULT_DEFINITIONS_MOCK[5]);
+
+  expect(
+    await ui.nameHeading('property.sonar.cobol.compilationConstants.name').find()
+  ).toBeInTheDocument();
+  expect(screen.getByRole('columnheader', { name: 'Name' })).toBeInTheDocument();
+  expect(screen.getByRole('columnheader', { name: 'Value' })).toBeInTheDocument();
+
+  // Should type new values
+  await user.type(ui.fieldsInput('name').get(), 'any name');
+  expect(ui.fieldsInput('name').getAll()).toHaveLength(2);
 
-  wrapper.instance().handleReset();
+  // Can cancel changes
+  await user.click(ui.cancelButton.get());
+  expect(ui.fieldsInput('name').getAll()).toHaveLength(1);
+  expect(ui.fieldsInput('name').get()).toHaveValue('');
 
-  expect(wrapper.state().loading).toBe(true);
+  // Can save new values
+  await user.type(ui.fieldsInput('name').get(), 'any name');
+  await user.type(ui.fieldsInput('value').getAll()[0], 'any value');
+  await user.click(ui.saveButton.get());
 
-  await waitAndUpdate(wrapper);
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+  expect(ui.fieldsInput('name').getAll()[0]).toHaveValue('any name');
+  expect(ui.fieldsInput('value').getAll()[0]).toHaveValue('any value');
 
-  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);
+  // Deleting previous value show validation message
+  await user.click(ui.deleteFieldsButton.get());
+  expect(ui.validationMsg.get()).toBeInTheDocument();
 
-  jest.runAllTimers();
-  expect(wrapper.state().success).toBe(false);
+  // Can reset to default
+  await user.click(ui.resetButton(/settings.definition.reset/).get());
+  await user.click(ui.resetButton().get());
+
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+  expect(ui.fieldsInput('name').get()).toHaveValue('');
+  expect(ui.fieldsInput('value').get()).toHaveValue('');
 });
 
-function shallowRender(props: Partial<Definition['props']> = {}) {
-  return shallow<Definition>(<Definition definition={mockDefinition()} {...props} />);
+it('renders secured definition and can do operations', async () => {
+  const user = userEvent.setup();
+  const key = `${DEFAULT_DEFINITIONS_MOCK[0].key}.secured`;
+  settingsMock.setDefinition(
+    mockDefinition({
+      ...DEFAULT_DEFINITIONS_MOCK[0],
+      key,
+    })
+  );
+  renderDefinition({
+    key,
+  });
+
+  expect(
+    await ui.nameHeading('property.sonar.announcement.message.secured.name').find()
+  ).toBeInTheDocument();
+
+  // Can type new value and cancel change
+  await user.type(ui.securedInput.get(), 'Anything');
+  expect(ui.securedInput.get()).toHaveValue('Anything');
+
+  // Can see validation message
+  await user.clear(ui.securedInput.get());
+  expect(ui.validationMsg.get()).toBeInTheDocument();
+
+  // Can cancel change
+  await user.click(ui.cancelButton.get());
+  expect(ui.securedInput.get()).toHaveValue('');
+
+  // Can save new value
+  await user.type(ui.securedInput.get(), 'Anything');
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+  expect(ui.securedInput.query()).not.toBeInTheDocument();
+
+  // Can change value by unlocking input
+  await user.click(ui.changeButton.get());
+  expect(ui.securedInput.get()).toBeInTheDocument();
+
+  // Cam reset to default
+  await user.click(ui.resetButton(/settings.definition.reset/).get());
+  await user.click(ui.resetButton().get());
+
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+});
+
+it('renders correctly for URL kind definition', async () => {
+  const user = userEvent.setup();
+  renderDefinition({ key: 'sonar.auth.gitlab.url' });
+
+  // Show validation message
+  await user.type(ui.urlKindInput.get(), 'wrongurl');
+  expect(ui.validationMsg.get()).toBeInTheDocument();
+  expect(ui.saveButton.get()).toBeDisabled();
+
+  // Hides validation msg with correct url
+  await user.type(ui.urlKindInput.get(), 'http://hi.there');
+  expect(ui.validationMsg.query()).not.toBeInTheDocument();
+  expect(ui.saveButton.get()).toBeEnabled();
+});
+
+function renderDefinition(
+  definition: Partial<ExtendedSettingDefinition> = {},
+  initialSetting?: SettingValue,
+  component?: Component
+) {
+  return renderComponent(
+    <Definition
+      definition={{ ...DEFAULT_DEFINITIONS_MOCK[0], ...definition }}
+      initialSettingValue={initialSetting}
+      component={component}
+    />
+  );
 }
index c95f2e18bb82b821f7d1ceaa24b800ad95b269b1..8d355a23ccf93f2932d83fa9c047a8102373dd56 100644 (file)
  * 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 userEvent from '@testing-library/user-event';
 import * as React from 'react';
-import { getNewCodePeriod, setNewCodePeriod } from '../../../../api/newCodePeriod';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { byRole, byText } from 'testing-library-selector';
+import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import NewCodePeriod from '../NewCodePeriod';
 
-jest.mock('../../../../api/newCodePeriod', () => ({
-  getNewCodePeriod: jest.fn().mockResolvedValue({}),
-  setNewCodePeriod: jest.fn(() => Promise.resolve()),
-}));
+let newCodeMock: NewCodePeriodsServiceMock;
 
-beforeEach(() => {
-  jest.clearAllMocks();
+beforeAll(() => {
+  newCodeMock = new NewCodePeriodsServiceMock();
 });
 
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
+afterEach(() => {
+  newCodeMock.reset();
 });
 
-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');
+const ui = {
+  newCodeTitle: byRole('heading', { name: 'settings.new_code_period.title' }),
+  savedMsg: byText('settings.state.saved'),
+  prevVersionRadio: byRole('radio', { name: /baseline.previous_version/ }),
+  daysNumberRadio: byRole('radio', { name: /baseline.number_days/ }),
+  daysInput: byRole('textbox'),
+  saveButton: byRole('button', { name: 'save' }),
+  cancelButton: byRole('button', { name: 'cancel' }),
+};
+
+it('renders and behaves as expected', async () => {
+  const user = userEvent.setup();
+  renderNewCodePeriod();
+
+  expect(await ui.newCodeTitle.find()).toBeInTheDocument();
+  // Previous version should be checked by default
+  expect(ui.prevVersionRadio.get()).toBeChecked();
+
+  // Can select number of days
+  await user.click(ui.daysNumberRadio.get());
+  expect(ui.daysNumberRadio.get()).toBeChecked();
+
+  // Save should be disabled for zero or NaN
+  expect(ui.daysInput.get()).toHaveValue('30');
+  await user.clear(ui.daysInput.get());
+  await user.type(ui.daysInput.get(), '0');
+  expect(await ui.saveButton.find()).toBeDisabled();
+  await user.clear(ui.daysInput.get());
+  await user.type(ui.daysInput.get(), 'asdas');
+  expect(ui.saveButton.get()).toBeDisabled();
+  await user.clear(ui.daysInput.get());
+
+  // Save enabled for valid days number
+  await user.type(ui.daysInput.get(), '10');
+  expect(ui.saveButton.get()).toBeEnabled();
+
+  // Can cancel action
+  await user.click(ui.cancelButton.get());
+  expect(ui.prevVersionRadio.get()).toBeChecked();
+
+  // Can save change
+  await user.click(ui.daysNumberRadio.get());
+  await user.type(ui.daysInput.get(), '10');
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
+
+  await user.click(ui.prevVersionRadio.get());
+  await user.click(ui.cancelButton.get());
+  await user.click(ui.prevVersionRadio.get());
+  await user.click(ui.saveButton.get());
+  expect(ui.savedMsg.get()).toBeInTheDocument();
 });
 
-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 />);
+function renderNewCodePeriod() {
+  return renderComponent(<NewCodePeriod />);
 }
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
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>
-`;
index 1070420b649f04ce829b00f9f76ab701bd6f5d2d..463e2b9666d2207eab1c007bfdbb76c638e50c8f 100644 (file)
@@ -75,7 +75,7 @@ afterEach(() => handler.resetValues());
 const ui = {
   saveButton: byRole('button', { name: 'settings.authentication.saml.form.save' }),
   customMessageInformation: byText('settings.authentication.custom_message_information'),
-  enabledToggle: byRole('button', { name: 'off' }),
+  enabledToggle: byRole('switch'),
   testButton: byText('settings.authentication.saml.form.test'),
   textbox1: byRole('textbox', { name: 'test1' }),
   textbox2: byRole('textbox', { name: 'test2' }),
@@ -172,7 +172,7 @@ describe('SAML tab', () => {
     await user.keyboard('new certificate');
     // enable the configuration
     await user.click(ui.enabledToggle.get());
-    expect(screen.getByRole('button', { name: 'on' })).toBeInTheDocument();
+    expect(ui.enabledToggle.get()).toBeChecked();
 
     await user.click(ui.saveButton.get());
     expect(screen.getByText('settings.authentication.saml.form.save_success')).toBeInTheDocument();
@@ -180,7 +180,7 @@ describe('SAML tab', () => {
     await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
     await user.click(screen.getByRole('tab', { name: 'SAML' }));
 
-    expect(screen.getByRole('button', { name: 'on' })).toBeInTheDocument();
+    expect(ui.enabledToggle.get()).toBeChecked();
   });
 
   it('should handle and show errors to the user', async () => {
index d752a6d397b510c28c430328605ebaed121ac812..1c1643fcc0093b3e9d865175a0df47e2bf399f9b 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import Toggle from '../../../../components/controls/Toggle';
+import Toggle, { getToggleValue } from '../../../../components/controls/Toggle';
 import { translate } from '../../../../helpers/l10n';
-import { DefaultSpecializedInputProps } from '../../utils';
+import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
 
 interface Props extends DefaultSpecializedInputProps {
   value: string | boolean | undefined;
 }
 
-export default function InputForBoolean({ onChange, name, value }: Props) {
-  const displayedValue = value != null ? value : false;
+export default function InputForBoolean({ onChange, name, value, setting }: Props) {
+  const toggleValue = getToggleValue(value != null ? value : false);
+
   return (
     <div className="display-inline-block text-top">
-      <Toggle name={name} onChange={onChange} value={displayedValue} />
+      <Toggle
+        name={name}
+        onChange={onChange}
+        value={toggleValue}
+        ariaLabel={getPropertyName(setting.definition)}
+      />
       {value == null && <span className="spacer-left note">{translate('settings.not_set')}</span>}
     </div>
   );
index d2c57097c433cdb72133411b0737082ad257e526..a1ef8a82097c15e360c6720826f647b0d775f967 100644 (file)
@@ -23,7 +23,7 @@ import { Button } from '../../../../components/controls/buttons';
 import EditIcon from '../../../../components/icons/EditIcon';
 import { translate } from '../../../../helpers/l10n';
 import { sanitizeUserInput } from '../../../../helpers/sanitize';
-import { DefaultSpecializedInputProps } from '../../utils';
+import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
 
 export default function InputForFormattedText(props: DefaultSpecializedInputProps) {
   const { isEditing, setting, name, value } = props;
@@ -41,6 +41,7 @@ export default function InputForFormattedText(props: DefaultSpecializedInputProp
       {editMode ? (
         <div className="display-flex-row">
           <textarea
+            aria-label={getPropertyName(setting.definition)}
             className="settings-large-input text-top spacer-right"
             name={name}
             onChange={handleInputChange}
index 9cccb2c1d6f0010c8389dd2e0a5482653eb4c9ef..15d45a9a1593f632ac4f72ec3ae07ceb519ff3fc 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 import { Button } from '../../../../components/controls/buttons';
 import { Alert } from '../../../../components/ui/Alert';
 import { translate } from '../../../../helpers/l10n';
-import { DefaultSpecializedInputProps } from '../../utils';
+import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
 
 const JSON_SPACE_SIZE = 4;
 
@@ -49,15 +49,18 @@ export default class InputForJSON extends React.PureComponent<DefaultSpecialized
   };
 
   render() {
+    const { value, name, setting } = this.props;
     const { formatError } = this.state;
+
     return (
       <div className="display-flex-end">
         <textarea
           className="settings-large-input text-top monospaced spacer-right"
-          name={this.props.name}
+          name={name}
           onChange={this.handleInputChange}
           rows={5}
-          value={this.props.value || ''}
+          value={value || ''}
+          aria-label={getPropertyName(setting.definition)}
         />
         <div>
           {formatError && <Alert variant="info">{translate('settings.json.format_error')} </Alert>}
index 6923d8f063e183a1c214dd9825c9108ad0b668b3..558e0f93ba575fc56034317e1ae48fb1a851f760 100644 (file)
@@ -25,6 +25,7 @@ import { translate } from '../../../../helpers/l10n';
 import {
   DefaultInputProps,
   DefaultSpecializedInputProps,
+  getPropertyName,
   getUniqueName,
   isDefaultOrInherited,
 } from '../../utils';
@@ -73,6 +74,7 @@ export default class InputForSecured extends React.PureComponent<Props, State> {
       <>
         <input className="hidden" type="password" />
         <Input
+          aria-label={getPropertyName(setting.definition)}
           autoComplete="off"
           className="js-setting-input settings-large-input"
           isDefault={isDefaultOrInherited(setting)}
index c3e41c1bf8b819d6a2acb893bcd0a082c4698eef..ec677d8d9da7cbb847cb5690c2846e4b588c1772 100644 (file)
@@ -20,7 +20,7 @@
 import * as React from 'react';
 import Select from '../../../../components/controls/Select';
 import { ExtendedSettingDefinition } from '../../../../types/settings';
-import { DefaultSpecializedInputProps } from '../../utils';
+import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
 
 type Props = DefaultSpecializedInputProps & Pick<ExtendedSettingDefinition, 'options'>;
 
@@ -30,7 +30,7 @@ export default class InputForSingleSelectList extends React.PureComponent<Props>
   };
 
   render() {
-    const { options: opts, name, value } = this.props;
+    const { options: opts, name, value, setting } = this.props;
 
     const options = opts.map((option) => ({
       label: option,
@@ -42,6 +42,7 @@ export default class InputForSingleSelectList extends React.PureComponent<Props>
         className="settings-large-input"
         name={name}
         onChange={this.handleInputChange}
+        aria-label={getPropertyName(setting.definition)}
         options={options}
         value={options.find((option) => option.value === value)}
       />
index 0d4bb30b33d768e2a823c1c4d6c012c1ef0c386a..cc81005adebb48ebfa0c9c9d09c0ff182a08676c 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { DefaultSpecializedInputProps } from '../../utils';
+import { DefaultSpecializedInputProps, getPropertyName } from '../../utils';
 
 export default class InputForText extends React.PureComponent<DefaultSpecializedInputProps> {
   handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
@@ -26,13 +26,15 @@ export default class InputForText extends React.PureComponent<DefaultSpecialized
   };
 
   render() {
+    const { setting, name, value } = this.props;
     return (
       <textarea
         className="settings-large-input text-top"
-        name={this.props.name}
+        name={name}
         onChange={this.handleInputChange}
         rows={5}
-        value={this.props.value || ''}
+        value={value || ''}
+        aria-label={getPropertyName(setting.definition)}
       />
     );
   }
index 80cb2eb7fd57722f6f63cd984a67348c7a71b120..2fdffa0ebb981342ee14430b1280d449f77f5b8e 100644 (file)
@@ -72,6 +72,7 @@ export default class MultiValueInput extends React.PureComponent<DefaultSpeciali
 
   render() {
     const displayedValue = [...this.ensureValue(), ...getEmptyValue(this.props.setting.definition)];
+
     return (
       <div>
         <ul>
index 0ebffee9f5d7cefe009af38067f2158d2c82c55b..39977561d40fae5a6d3e05908decd06f733a7f88 100644 (file)
  */
 import * as React from 'react';
 import { DeleteButton } from '../../../../components/controls/buttons';
+import { translateWithParameters } from '../../../../helpers/l10n';
 import {
   DefaultSpecializedInputProps,
   getEmptyValue,
+  getPropertyName,
   getUniqueName,
   isCategoryDefinition,
 } from '../../utils';
@@ -75,6 +77,11 @@ export default class PropertySetInput extends React.PureComponent<DefaultSpecial
         <td className="thin nowrap text-middle">
           {!isLast && (
             <DeleteButton
+              aria-label={translateWithParameters(
+                'settings.definitions.delete_fields',
+                getPropertyName(setting.definition),
+                index
+              )}
               className="js-remove-value"
               onClick={() => this.handleDeleteValue(index)}
             />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx
deleted file mode 100644 (file)
index e575d17..0000000
+++ /dev/null
@@ -1,80 +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, mockSetting } from '../../../../../helpers/mocks/settings';
-import { Setting, SettingType } from '../../../../../types/settings';
-import { DefaultInputProps } from '../../../utils';
-import Input from '../Input';
-import InputForSecured from '../InputForSecured';
-import MultiValueInput from '../MultiValueInput';
-import PrimitiveInput from '../PrimitiveInput';
-import PropertySetInput from '../PropertySetInput';
-
-it('should render PrimitiveInput', () => {
-  const onChange = jest.fn();
-  const input = shallowRender({ onChange }).find(PrimitiveInput);
-  expect(input.length).toBe(1);
-  expect(input.prop('value')).toBe('foo');
-  expect(input.prop('onChange')).toBe(onChange);
-});
-
-it('should render Secured input', () => {
-  const setting: Setting = mockSetting({
-    key: 'foo.secured',
-    definition: mockDefinition({ key: 'foo.secured', type: SettingType.PROPERTY_SET }),
-  });
-  const onChange = jest.fn();
-  const input = shallowRender({ onChange, setting }).find(InputForSecured);
-  expect(input.length).toBe(1);
-  expect(input.prop('value')).toBe('foo');
-  expect(input.prop('onChange')).toBe(onChange);
-});
-
-it('should render MultiValueInput', () => {
-  const setting = mockSetting({
-    definition: mockDefinition({ multiValues: true }),
-  });
-  const onChange = jest.fn();
-  const value = ['foo', 'bar'];
-  const input = shallowRender({ onChange, setting, value }).find(MultiValueInput);
-  expect(input.length).toBe(1);
-  expect(input.prop('setting')).toBe(setting);
-  expect(input.prop('value')).toBe(value);
-  expect(input.prop('onChange')).toBe(onChange);
-});
-
-it('should render PropertySetInput', () => {
-  const setting: Setting = mockSetting({
-    definition: mockDefinition({ type: SettingType.PROPERTY_SET }),
-  });
-
-  const onChange = jest.fn();
-  const value = [{ foo: 'bar' }];
-  const input = shallowRender({ onChange, setting, value }).find(PropertySetInput);
-  expect(input.length).toBe(1);
-  expect(input.prop('setting')).toBe(setting);
-  expect(input.prop('value')).toBe(value);
-  expect(input.prop('onChange')).toBe(onChange);
-});
-
-function shallowRender(props: Partial<DefaultInputProps> = {}) {
-  return shallow(<Input onChange={jest.fn()} setting={mockSetting()} value="foo" {...props} />);
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx
deleted file mode 100644 (file)
index f8f3aa5..0000000
+++ /dev/null
@@ -1,70 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import InputForBoolean from '../InputForBoolean';
-
-it('should render Toggle', () => {
-  const onChange = jest.fn();
-  const toggle = shallowRender({ onChange }).find('Toggle');
-  expect(toggle.length).toBe(1);
-  expect(toggle.prop('name')).toBe('foo');
-  expect(toggle.prop('value')).toBe(true);
-  expect(toggle.prop('onChange')).toBeDefined();
-});
-
-it('should render Toggle without value', () => {
-  const onChange = jest.fn();
-  const input = shallowRender({ onChange, value: undefined });
-  const toggle = input.find('Toggle');
-  expect(toggle.length).toBe(1);
-  expect(toggle.prop('name')).toBe('foo');
-  expect(toggle.prop('value')).toBe(false);
-  expect(toggle.prop('onChange')).toBeDefined();
-  expect(input.find('.note').length).toBe(1);
-});
-
-it('should call onChange', () => {
-  const onChange = jest.fn();
-
-  const input = shallowRender({ onChange, value: true });
-  const toggle = input.find('Toggle');
-  expect(toggle.length).toBe(1);
-  expect(toggle.prop('onChange')).toBeDefined();
-
-  toggle.prop<Function>('onChange')(false);
-
-  expect(onChange).toHaveBeenCalledWith(false);
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
-  return shallow(
-    <InputForBoolean
-      isDefault={false}
-      name="foo"
-      onChange={jest.fn()}
-      setting={mockSetting()}
-      value={true}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForFormattedText-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForFormattedText-test.tsx
deleted file mode 100644 (file)
index 7412bb5..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 { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-import { mockSetting } from '../../../../../helpers/mocks/settings';
-import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import InputForFormattedText from '../InputForFormattedText';
-
-it('should render correctly with no value for login message', () => {
-  renderInputForFormattedText();
-  expect(screen.getByRole('textbox')).toBeInTheDocument();
-});
-
-it('should render correctly with a value for login message', () => {
-  renderInputForFormattedText({
-    setting: mockSetting({ values: ['*text*', 'text'], hasValue: true }),
-  });
-  expect(screen.getByRole('button', { name: 'edit' })).toBeInTheDocument();
-  expect(screen.getByText('text')).toBeInTheDocument();
-});
-
-it('should render correctly with a value for login message if hasValue is set', () => {
-  renderInputForFormattedText({
-    setting: mockSetting({ hasValue: true }),
-  });
-  expect(screen.getByRole('button', { name: 'edit' })).toBeInTheDocument();
-});
-
-it('should render editMode when value is empty', () => {
-  renderInputForFormattedText({
-    value: '',
-  });
-  expect(screen.getByRole('textbox')).toBeInTheDocument();
-  expect(screen.queryByRole('button', { name: 'edit' })).not.toBeInTheDocument();
-});
-
-it('should render correctly if in editMode', async () => {
-  const user = userEvent.setup();
-  const onChange = jest.fn();
-
-  renderInputForFormattedText({
-    setting: mockSetting({ values: ['*text*', 'text'], hasValue: true }),
-    isEditing: true,
-    onChange,
-  });
-  expect(screen.getByRole('textbox')).toBeInTheDocument();
-  expect(screen.queryByRole('button', { name: 'edit' })).not.toBeInTheDocument();
-
-  await user.click(screen.getByRole('textbox'));
-  await user.keyboard('N');
-  expect(onChange).toHaveBeenCalledTimes(1);
-});
-
-function renderInputForFormattedText(props: Partial<DefaultSpecializedInputProps> = {}) {
-  renderComponent(
-    <InputForFormattedText
-      onEditing={jest.fn()}
-      isEditing={false}
-      isDefault={true}
-      name="name"
-      onChange={jest.fn()}
-      setting={mockSetting({ value: undefined, hasValue: false })}
-      value="*text*"
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx
deleted file mode 100644 (file)
index d6a1c26..0000000
+++ /dev/null
@@ -1,80 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import { change } from '../../../../../helpers/testUtils';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import InputForJSON from '../InputForJSON';
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should call onChange', () => {
-  const onChange = jest.fn();
-  const wrapper = shallowRender({ onChange });
-
-  change(wrapper.find('textarea'), '{"a": 1}');
-  expect(onChange).toHaveBeenCalledWith('{"a": 1}');
-});
-
-it('should handle formatting for invalid JSON', () => {
-  const onChange = jest.fn();
-  const wrapper = shallowRender({ onChange, value: '{"a": 1b}' });
-  wrapper.instance().format();
-  expect(onChange).not.toHaveBeenCalled();
-
-  expect(wrapper.state().formatError).toBe(true);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should handle formatting for valid JSON', () => {
-  const onChange = jest.fn();
-  const wrapper = shallowRender({ onChange, value: '{"a": 1}' });
-  wrapper.instance().format();
-  expect(onChange).toHaveBeenCalledWith(`{
-    "a": 1
-}`);
-
-  expect(wrapper.state().formatError).toBe(false);
-});
-
-it('should handle ignore formatting if empty', () => {
-  const onChange = jest.fn();
-  const wrapper = shallowRender({ onChange, value: '' });
-  wrapper.instance().format();
-  expect(onChange).not.toHaveBeenCalled();
-
-  expect(wrapper.state().formatError).toBe(false);
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
-  return shallow<InputForJSON>(
-    <InputForJSON
-      isDefault={false}
-      name="foo"
-      onChange={jest.fn()}
-      setting={mockSetting()}
-      value=""
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx
deleted file mode 100644 (file)
index e22ba04..0000000
+++ /dev/null
@@ -1,42 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import InputForNumber from '../InputForNumber';
-import SimpleInput from '../SimpleInput';
-
-it('should render SimpleInput', () => {
-  const onChange = jest.fn();
-  const simpleInput = shallow(
-    <InputForNumber
-      isDefault={false}
-      name="foo"
-      onChange={onChange}
-      setting={mockSetting()}
-      value={17}
-    />
-  ).find(SimpleInput);
-  expect(simpleInput.length).toBe(1);
-  expect(simpleInput.prop('name')).toBe('foo');
-  expect(simpleInput.prop('value')).toBe(17);
-  expect(simpleInput.prop('type')).toBe('text');
-  expect(simpleInput.prop('onChange')).toBeDefined();
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx
deleted file mode 100644 (file)
index eba4748..0000000
+++ /dev/null
@@ -1,42 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import InputForPassword from '../InputForPassword';
-import SimpleInput from '../SimpleInput';
-
-it('should render SimpleInput', () => {
-  const onChange = jest.fn();
-  const simpleInput = shallow(
-    <InputForPassword
-      isDefault={false}
-      name="foo"
-      onChange={onChange}
-      setting={mockSetting()}
-      value="bar"
-    />
-  ).find(SimpleInput);
-  expect(simpleInput.length).toBe(1);
-  expect(simpleInput.prop('name')).toBe('foo');
-  expect(simpleInput.prop('value')).toBe('bar');
-  expect(simpleInput.prop('type')).toBe('password');
-  expect(simpleInput.prop('onChange')).toBeDefined();
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx
deleted file mode 100644 (file)
index a659d8c..0000000
+++ /dev/null
@@ -1,96 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import { change, click } from '../../../../../helpers/testUtils';
-import InputForSecured from '../InputForSecured';
-import InputForString from '../InputForString';
-
-it('should render lock icon, but no form', () => {
-  const onChange = jest.fn();
-  const input = shallowRender({ onChange });
-
-  expect(input.find('LockIcon').length).toBe(1);
-  expect(input.find('input').length).toBe(0);
-});
-
-it('should open form', () => {
-  const onChange = jest.fn();
-  const input = shallowRender({ onChange });
-  const button = input.find('Button');
-  expect(button.length).toBe(1);
-
-  click(button);
-  expect(input.find('input').length).toBe(1);
-});
-
-it('should set value', () => {
-  const onChange = jest.fn(() => Promise.resolve());
-  const input = shallowRender({ onChange });
-
-  click(input.find('Button'));
-  change(input.find(InputForString), 'secret');
-  expect(onChange).toHaveBeenCalledWith('secret');
-});
-
-it('should show input when empty, and enable handle typing', () => {
-  const input = shallowRender({ setting: mockSetting({ hasValue: false }) });
-  const onChange = (value: string) => input.setProps({ hasValueChanged: true, value });
-  input.setProps({ onChange });
-
-  expect(input.find('input').length).toBe(1);
-  change(input.find(InputForString), 'hello');
-  expect(input.find('input').length).toBe(1);
-  expect(input.find(InputForString).prop('value')).toBe('hello');
-});
-
-it('should handle value reset', () => {
-  const input = shallowRender({ hasValueChanged: true, value: 'whatever' });
-  input.setState({ changing: true });
-
-  // reset
-  input.setProps({ hasValueChanged: false, value: 'original' });
-
-  expect(input.state('changing')).toBe(false);
-});
-
-it('should handle value reset to empty', () => {
-  const input = shallowRender({ hasValueChanged: true, value: 'whatever' });
-  input.setState({ changing: true });
-
-  // outside change
-  input.setProps({ hasValueChanged: false, setting: mockSetting({ hasValue: false }) });
-
-  expect(input.state('changing')).toBe(true);
-});
-
-function shallowRender(props: Partial<InputForSecured['props']> = {}) {
-  return shallow<InputForSecured>(
-    <InputForSecured
-      input={InputForString}
-      hasValueChanged={false}
-      onChange={jest.fn()}
-      setting={mockSetting()}
-      value="bar"
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx
deleted file mode 100644 (file)
index 0af1e4e..0000000
+++ /dev/null
@@ -1,63 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import InputForSingleSelectList from '../InputForSingleSelectList';
-
-it('should render Select', () => {
-  const onChange = jest.fn();
-  const select = shallowRender({ onChange }).find(Select);
-  expect(select.length).toBe(1);
-  expect(select.prop('name')).toBe('foo');
-  expect(select.prop('value')).toEqual({ label: 'bar', value: 'bar' });
-  expect(select.prop('options')).toEqual([
-    { value: 'foo', label: 'foo' },
-    { value: 'bar', label: 'bar' },
-    { value: 'baz', label: 'baz' },
-  ]);
-  expect(select.prop('onChange')).toBeDefined();
-});
-
-it('should call onChange', () => {
-  const onChange = jest.fn();
-  const select = shallowRender({ onChange }).find(Select);
-  expect(select.length).toBe(1);
-  expect(select.prop('onChange')).toBeDefined();
-
-  select.prop<Function>('onChange')({ value: 'baz', label: 'baz' });
-  expect(onChange).toHaveBeenCalledWith('baz');
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
-  return shallow(
-    <InputForSingleSelectList
-      isDefault={false}
-      name="foo"
-      onChange={jest.fn()}
-      options={['foo', 'bar', 'baz']}
-      setting={mockSetting()}
-      value="bar"
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx
deleted file mode 100644 (file)
index b1ae445..0000000
+++ /dev/null
@@ -1,42 +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 { mockSetting } from '../../../../../helpers/mocks/settings';
-import InputForString from '../InputForString';
-import SimpleInput from '../SimpleInput';
-
-it('should render SimpleInput', () => {
-  const onChange = jest.fn();
-  const simpleInput = shallow(
-    <InputForString
-      isDefault={false}
-      name="foo"
-      onChange={onChange}
-      setting={mockSetting()}
-      value="bar"
-    />
-  ).find(SimpleInput);
-  expect(simpleInput.length).toBe(1);
-  expect(simpleInput.prop('name')).toBe('foo');
-  expect(simpleInput.prop('value')).toBe('bar');
-  expect(simpleInput.prop('type')).toBe('text');
-  expect(simpleInput.prop('onChange')).toBeDefined();
-});
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx
deleted file mode 100644 (file)
index f9f2d29..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockSetting } from '../../../../../helpers/mocks/settings';
-import { change } from '../../../../../helpers/testUtils';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import InputForText from '../InputForText';
-
-it('should render textarea', () => {
-  const onChange = jest.fn();
-  const textarea = shallowRender({ onChange }).find('textarea');
-  expect(textarea.length).toBe(1);
-  expect(textarea.prop('name')).toBe('foo');
-  expect(textarea.prop('value')).toBe('bar');
-  expect(textarea.prop('onChange')).toBeDefined();
-});
-
-it('should call onChange', () => {
-  const onChange = jest.fn();
-  const textarea = shallowRender({ onChange }).find('textarea');
-  expect(textarea.length).toBe(1);
-  expect(textarea.prop('onChange')).toBeDefined();
-
-  change(textarea, 'qux');
-  expect(onChange).toHaveBeenCalledWith('qux');
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
-  return shallow(
-    <InputForText
-      isDefault={false}
-      name="foo"
-      onChange={jest.fn()}
-      value="bar"
-      {...props}
-      setting={mockSetting()}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx
deleted file mode 100644 (file)
index 86d02a8..0000000
+++ /dev/null
@@ -1,96 +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, ShallowWrapper } from 'enzyme';
-import * as React from 'react';
-import { click } from '../../../../../helpers/testUtils';
-import { ExtendedSettingDefinition, SettingType } from '../../../../../types/settings';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import MultiValueInput from '../MultiValueInput';
-import PrimitiveInput from '../PrimitiveInput';
-
-const settingValue = {
-  key: 'example',
-  hasValue: true,
-};
-
-const settingDefinition: ExtendedSettingDefinition = {
-  category: 'general',
-  fields: [],
-  key: 'example',
-  multiValues: true,
-  options: [],
-  subCategory: 'Branches',
-  type: SettingType.STRING,
-};
-
-const assertValues = (inputs: ShallowWrapper<any>, values: string[]) => {
-  values.forEach((value, index) => {
-    const input = inputs.at(index);
-    expect(input.prop('value')).toBe(value);
-  });
-};
-
-it('should render one value', () => {
-  const multiValueInput = shallowRender();
-  const stringInputs = multiValueInput.find(PrimitiveInput);
-  expect(stringInputs.length).toBe(1 + 1);
-  assertValues(stringInputs, ['foo', '']);
-});
-
-it('should render several values', () => {
-  const multiValueInput = shallowRender({ value: ['foo', 'bar', 'baz'] });
-  const stringInputs = multiValueInput.find(PrimitiveInput);
-  expect(stringInputs.length).toBe(3 + 1);
-  assertValues(stringInputs, ['foo', 'bar', 'baz', '']);
-});
-
-it('should remove value', () => {
-  const onChange = jest.fn();
-  const multiValueInput = shallowRender({ onChange, value: ['foo', 'bar', 'baz'] });
-  click(multiValueInput.find('.js-remove-value').at(1));
-  expect(onChange).toHaveBeenCalledWith(['foo', 'baz']);
-});
-
-it('should change existing value', () => {
-  const onChange = jest.fn();
-  const multiValueInput = shallowRender({ onChange, value: ['foo', 'bar', 'baz'] });
-  multiValueInput.find(PrimitiveInput).at(1).prop('onChange')('qux');
-  expect(onChange).toHaveBeenCalledWith(['foo', 'qux', 'baz']);
-});
-
-it('should add new value', () => {
-  const onChange = jest.fn();
-  const multiValueInput = shallowRender({ onChange });
-  multiValueInput.find(PrimitiveInput).at(1).prop('onChange')('bar');
-  expect(onChange).toHaveBeenCalledWith(['foo', 'bar']);
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
-  return shallow(
-    <MultiValueInput
-      isDefault={true}
-      name="bar"
-      onChange={jest.fn()}
-      setting={{ ...settingValue, definition: settingDefinition }}
-      value={['foo']}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx
deleted file mode 100644 (file)
index df337a9..0000000
+++ /dev/null
@@ -1,46 +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, mockSetting } from '../../../../../helpers/mocks/settings';
-import { SettingType } from '../../../../../types/settings';
-import { DefaultSpecializedInputProps } from '../../../utils';
-import PrimitiveInput from '../PrimitiveInput';
-
-it.each(Object.values(SettingType).map(Array.of))(
-  'should render correctly for %s',
-  (type: SettingType) => {
-    const setting = mockSetting({ definition: mockDefinition({ type }) });
-    expect(shallowRender({ setting })).toMatchSnapshot();
-  }
-);
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
-  return shallow(
-    <PrimitiveInput
-      isDefault={true}
-      name="name"
-      onChange={jest.fn()}
-      setting={mockSetting()}
-      value={['foo']}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx
deleted file mode 100644 (file)
index 577e0d4..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 { KeyboardKeys } from '../../../../../helpers/keycodes';
-import { mockSetting } from '../../../../../helpers/mocks/settings';
-import { change, mockEvent } from '../../../../../helpers/testUtils';
-import SimpleInput, { SimpleInputProps } from '../SimpleInput';
-
-it('should render input', () => {
-  const input = shallowRender().find('input');
-  expect(input.length).toBe(1);
-  expect(input.prop('type')).toBe('text');
-  expect(input.prop('className')).toContain('input-large');
-  expect(input.prop('name')).toBe('foo');
-  expect(input.prop('value')).toBe('bar');
-  expect(input.prop('onChange')).toBeDefined();
-});
-
-it('should call onChange', () => {
-  const onChange = jest.fn();
-  const input = shallowRender({ onChange }).find('input');
-  expect(input.length).toBe(1);
-  expect(input.prop('onChange')).toBeDefined();
-
-  change(input, 'qux');
-  expect(onChange).toHaveBeenCalledWith('qux');
-});
-
-it('should handle enter', () => {
-  const onSave = jest.fn();
-  shallowRender({ onSave })
-    .instance()
-    .handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.Enter } }));
-  expect(onSave).toHaveBeenCalled();
-});
-
-it('should handle esc', () => {
-  const onCancel = jest.fn();
-  shallowRender({ onCancel })
-    .instance()
-    .handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.Escape } }));
-  expect(onCancel).toHaveBeenCalled();
-});
-
-it('should ignore other keys', () => {
-  const onSave = jest.fn();
-  const onCancel = jest.fn();
-  shallowRender({ onCancel, onSave })
-    .instance()
-    .handleKeyDown(mockEvent({ nativeEvent: { key: KeyboardKeys.LeftArrow } }));
-  expect(onSave).not.toHaveBeenCalled();
-  expect(onCancel).not.toHaveBeenCalled();
-});
-
-function shallowRender(overrides: Partial<SimpleInputProps> = {}) {
-  return shallow<SimpleInput>(
-    <SimpleInput
-      className="input-large"
-      isDefault={false}
-      name="foo"
-      onChange={jest.fn()}
-      type="text"
-      setting={mockSetting()}
-      value="bar"
-      {...overrides}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/InputForJSON-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/InputForJSON-test.tsx.snap
deleted file mode 100644 (file)
index 3353ed9..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should handle formatting for invalid JSON 1`] = `
-<div
-  className="display-flex-end"
->
-  <textarea
-    className="settings-large-input text-top monospaced spacer-right"
-    name="foo"
-    onChange={[Function]}
-    rows={5}
-    value="{"a": 1b}"
-  />
-  <div>
-    <Alert
-      variant="info"
-    >
-      settings.json.format_error
-       
-    </Alert>
-    <Button
-      className="spacer-top"
-      onClick={[Function]}
-    >
-      settings.json.format
-    </Button>
-  </div>
-</div>
-`;
-
-exports[`should render correctly 1`] = `
-<div
-  className="display-flex-end"
->
-  <textarea
-    className="settings-large-input text-top monospaced spacer-right"
-    name="foo"
-    onChange={[Function]}
-    rows={5}
-    value=""
-  />
-  <div>
-    <Button
-      className="spacer-top"
-      onClick={[Function]}
-    >
-      settings.json.format
-    </Button>
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap
deleted file mode 100644 (file)
index ddfa45a..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly for BOOLEAN 1`] = `
-<InputForBoolean
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "BOOLEAN",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for FLOAT 1`] = `
-<InputForNumber
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "FLOAT",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for FORMATTED_TEXT 1`] = `
-<InputForFormattedText
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "FORMATTED_TEXT",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for INTEGER 1`] = `
-<InputForNumber
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "INTEGER",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for JSON 1`] = `
-<InputForJSON
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "JSON",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for LICENSE 1`] = `
-<InputForString
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "LICENSE",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for LONG 1`] = `
-<InputForNumber
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "LONG",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for PASSWORD 1`] = `
-<InputForPassword
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "PASSWORD",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for PROPERTY_SET 1`] = `
-<InputForString
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "PROPERTY_SET",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for SINGLE_SELECT_LIST 1`] = `
-<Wrapped
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "SINGLE_SELECT_LIST",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for STRING 1`] = `
-<InputForString
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "STRING",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
-
-exports[`should render correctly for TEXT 1`] = `
-<InputForText
-  isDefault={true}
-  name="name"
-  onChange={[MockFunction]}
-  setting={
-    {
-      "definition": {
-        "category": "foo category",
-        "fields": [],
-        "key": "foo",
-        "options": [],
-        "subCategory": "foo subCat",
-        "type": "TEXT",
-      },
-      "hasValue": true,
-      "inherited": true,
-      "key": "foo",
-      "value": "42",
-    }
-  }
-  value={
-    [
-      "foo",
-    ]
-  }
-/>
-`;
index 9774b41a627ecb3770da1312a3eaed293b3cd2ab..008c393d26022fad4d8c46ceeb53c9d418206cf1 100644 (file)
@@ -19,7 +19,6 @@
  */
 import classNames from 'classnames';
 import * as React from 'react';
-import { translate } from '../../helpers/l10n';
 import CheckIcon from '../icons/CheckIcon';
 import { Button } from './buttons';
 import './Toggle.css';
@@ -32,10 +31,14 @@ interface Props {
   value: boolean | string;
 }
 
+export function getToggleValue(value: string | boolean) {
+  return typeof value === 'string' ? value === 'true' : value;
+}
+
 export default class Toggle extends React.PureComponent<Props> {
   getValue = () => {
     const { value } = this.props;
-    return typeof value === 'string' ? value === 'true' : value;
+    return getToggleValue(value);
   };
 
   handleClick = () => {
@@ -51,11 +54,16 @@ export default class Toggle extends React.PureComponent<Props> {
     const className = classNames('boolean-toggle', { 'boolean-toggle-on': value });
 
     return (
-      <Button className={className} disabled={disabled} name={name} onClick={this.handleClick}>
-        <div
-          aria-label={ariaLabel ?? translate(value ? 'on' : 'off')}
-          className="boolean-toggle-handle"
-        >
+      <Button
+        className={className}
+        disabled={disabled}
+        aria-label={ariaLabel}
+        name={name}
+        onClick={this.handleClick}
+        role="switch"
+        aria-checked={value}
+      >
+        <div className="boolean-toggle-handle">
           <CheckIcon size={12} />
         </div>
       </Button>
index 8862993eb41bf5c6a120fef74cb4f5356083f46b..ac7aa845377452c44b130e93ac40cee78b93ab67 100644 (file)
@@ -2,13 +2,14 @@
 
 exports[`should render correctly: disabled 1`] = `
 <Button
+  aria-checked={true}
   className="boolean-toggle boolean-toggle-on"
   disabled={true}
   name="toggle-name"
   onClick={[Function]}
+  role="switch"
 >
   <div
-    aria-label="on"
     className="boolean-toggle-handle"
   >
     <CheckIcon
@@ -20,13 +21,14 @@ exports[`should render correctly: disabled 1`] = `
 
 exports[`should render correctly: off 1`] = `
 <Button
+  aria-checked={false}
   className="boolean-toggle"
   disabled={true}
   name="toggle-name"
   onClick={[Function]}
+  role="switch"
 >
   <div
-    aria-label="off"
     className="boolean-toggle-handle"
   >
     <CheckIcon
@@ -38,13 +40,14 @@ exports[`should render correctly: off 1`] = `
 
 exports[`should render correctly: on 1`] = `
 <Button
+  aria-checked={true}
   className="boolean-toggle boolean-toggle-on"
   disabled={true}
   name="toggle-name"
   onClick={[Function]}
+  role="switch"
 >
   <div
-    aria-label="on"
     className="boolean-toggle-handle"
   >
     <CheckIcon
index 30e037e39b17d5a32cc7e322f91b5ca2cf61c6e3..5137c5458fa27930612a42bad6a3ea3bd22adea2 100644 (file)
@@ -41,6 +41,7 @@ type AllowedButtonAttributes = Pick<
   | 'onMouseOver'
   | 'onMouseLeave'
   | 'tabIndex'
+  | 'role'
 >;
 
 interface ButtonProps extends AllowedButtonAttributes {
index a3b58d104915437647e094fe34b95f76d14f1556..59e62a8bf8d8757961abe8c5078cfffaedd6ee8b 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 { NewCodePeriodSettingType } from '../../types/types';
 import { getPeriodLabel } from '../periods';
 import { mockPeriod } from '../testMocks';
 
@@ -85,21 +86,27 @@ describe('getPeriodLabel', () => {
 
   it('should handle SPECIFIC_ANALYSIS', () => {
     expect(
-      getPeriodLabel(mockPeriod({ mode: 'SPECIFIC_ANALYSIS', parameter: '7.1' }), formatter)
+      getPeriodLabel(
+        mockPeriod({ mode: NewCodePeriodSettingType.SPECIFIC_ANALYSIS, parameter: '7.1' }),
+        formatter
+      )
+    ).toBe('overview.period.specific_analysis.2019-04-23T02:12:32+0100');
+    expect(
+      getPeriodLabel(mockPeriod({ mode: NewCodePeriodSettingType.SPECIFIC_ANALYSIS }), formatter)
     ).toBe('overview.period.specific_analysis.2019-04-23T02:12:32+0100');
-    expect(getPeriodLabel(mockPeriod({ mode: 'SPECIFIC_ANALYSIS' }), formatter)).toBe(
-      'overview.period.specific_analysis.2019-04-23T02:12:32+0100'
-    );
     expect(formatter).toHaveBeenCalledTimes(2);
   });
 
   it('should handle PREVIOUS_VERSION', () => {
     expect(
-      getPeriodLabel(mockPeriod({ mode: 'PREVIOUS_VERSION', modeParam: 'A658678DE' }), formatter)
+      getPeriodLabel(
+        mockPeriod({ mode: NewCodePeriodSettingType.PREVIOUS_VERSION, modeParam: 'A658678DE' }),
+        formatter
+      )
     ).toBe('overview.period.previous_version.A658678DE');
-    expect(getPeriodLabel(mockPeriod({ mode: 'PREVIOUS_VERSION' }), formatter)).toBe(
-      'overview.period.previous_version.2019-04-23T02:12:32+0100'
-    );
+    expect(
+      getPeriodLabel(mockPeriod({ mode: NewCodePeriodSettingType.PREVIOUS_VERSION }), formatter)
+    ).toBe('overview.period.previous_version.2019-04-23T02:12:32+0100');
     expect(formatter).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/server/sonar-web/src/main/js/helpers/mocks/new-code-period.ts b/server/sonar-web/src/main/js/helpers/mocks/new-code-period.ts
new file mode 100644 (file)
index 0000000..b33d629
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * 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 { NewCodePeriod, NewCodePeriodSettingType } from '../../types/types';
+
+export function mockNewCodePeriod(overrides: Partial<NewCodePeriod> = {}) {
+  return {
+    type: NewCodePeriodSettingType.PREVIOUS_VERSION,
+    ...overrides,
+  };
+}
index a43ee13ac2e810de696cb35fec191e700a0549af..92e7588b44e059ed778db4987ed2fa8cc2771a4f 100644 (file)
@@ -20,6 +20,7 @@
 import {
   ExtendedSettingDefinition,
   Setting,
+  SettingFieldDefinition,
   SettingType,
   SettingValue,
   SettingWithCategory,
@@ -38,6 +39,12 @@ export function mockDefinition(
   };
 }
 
+export function mockSettingFieldDefinition(
+  overrides: Partial<SettingFieldDefinition> = {}
+): SettingFieldDefinition {
+  return { key: 'name', name: 'Name', options: [], ...overrides };
+}
+
 export function mockSetting(overrides: Partial<Setting> = {}): Setting {
   return {
     key: 'foo',
index 29434d4d2d10d225109f2c52f8161b372d347730..376a61c2a9c659289d3a17de51641c8433dac345 100644 (file)
@@ -20,7 +20,7 @@
 import { parseDate } from '../helpers/dates';
 import { translate, translateWithParameters } from '../helpers/l10n';
 import { ApplicationPeriod } from '../types/application';
-import { Period } from '../types/types';
+import { NewCodePeriodSettingType, Period } from '../types/types';
 
 export function getPeriodLabel(
   period: Period | undefined,
@@ -33,10 +33,10 @@ export function getPeriodLabel(
   let parameter = period.modeParam || period.parameter || '';
 
   switch (period.mode) {
-    case 'SPECIFIC_ANALYSIS':
+    case NewCodePeriodSettingType.SPECIFIC_ANALYSIS:
       parameter = dateFormatter(period.date);
       break;
-    case 'PREVIOUS_VERSION':
+    case NewCodePeriodSettingType.PREVIOUS_VERSION:
       parameter = parameter || dateFormatter(period.date);
       break;
     /*
index 819c1c0a08c5733315edbb362e8e3cc25e11e582..2412af05bf4d3c97941eb108732734163aad592c 100644 (file)
@@ -74,7 +74,6 @@ export interface SettingDefinition {
 }
 
 export interface SettingFieldDefinition extends SettingDefinition {
-  description: string;
   name: string;
 }
 
index 4a7bc7ab2e6b11fc77ced5f5951d412a3e92f79b..b921015846d8ebe68c403e6a6dc8b7391671af6a 100644 (file)
@@ -405,11 +405,12 @@ export interface NewCodePeriodBranch {
   effectiveValue?: string;
 }
 
-export type NewCodePeriodSettingType =
-  | 'PREVIOUS_VERSION'
-  | 'NUMBER_OF_DAYS'
-  | 'SPECIFIC_ANALYSIS'
-  | 'REFERENCE_BRANCH';
+export enum NewCodePeriodSettingType {
+  PREVIOUS_VERSION = 'PREVIOUS_VERSION',
+  NUMBER_OF_DAYS = 'NUMBER_OF_DAYS',
+  SPECIFIC_ANALYSIS = 'SPECIFIC_ANALYSIS',
+  REFERENCE_BRANCH = 'REFERENCE_BRANCH',
+}
 
 export interface Paging {
   pageIndex: number;
index 7489c7bdc5c9a7078c2cf0ba25062f8e3c6f7c7f..9cd62fcc56e63642c98bbbac0c8157b63aaae3ac 100644 (file)
@@ -140,8 +140,6 @@ new_name=New name
 none=None
 no_tags=No tags
 not_now=Not now
-off=Off
-on=On
 or=Or
 open=Open
 optional=Optional
@@ -244,7 +242,6 @@ no=No
 valid_input=Valid input
 
 
-
 #------------------------------------------------------------------------------
 #
 # GENERIC EXPRESSIONS, sorted alphabetically
@@ -1164,6 +1161,7 @@ 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.definitions.delete_fields=Delete row {1} for setting "{0}"
 
 settings.search.placeholder=Find in Settings
 settings.search.results=Search results list