diff options
author | stanislavh <stanislav.honcharov@sonarsource.com> | 2023-03-03 13:48:04 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-03-06 20:03:37 +0000 |
commit | 2ef9e45cf45c5fb14c61981f880249eb36dc0029 (patch) | |
tree | 3489bca878b32f177bfcd941c205e22577c8b26f /server/sonar-web | |
parent | e2874ce9c4a4fc154e51b8e26a80ec2281eb7628 (diff) | |
download | sonarqube-2ef9e45cf45c5fb14c61981f880249eb36dc0029.tar.gz sonarqube-2ef9e45cf45c5fb14c61981f880249eb36dc0029.zip |
SONAR-18593 Migrate rtl for settings
Diffstat (limited to 'server/sonar-web')
68 files changed, 858 insertions, 1730 deletions
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 index 00000000000..df8b3b6ba57 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts @@ -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)); + } +} diff --git a/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts index abfd964dcb8..b22051af84a 100644 --- a/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts @@ -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); diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx index 02ec79d5a74..a740d347879 100644 --- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx +++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx @@ -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(); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx index 812d7f22130..f6f5acb46a5 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/LeakPeriodLegend.tsx @@ -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; } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx index e75f4fe0a6a..37d8c2ac37f 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelNoNewCode.tsx @@ -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 diff --git a/server/sonar-web/src/main/js/apps/overview/branches/ProjectLeakPeriodInfo.tsx b/server/sonar-web/src/main/js/apps/overview/branches/ProjectLeakPeriodInfo.tsx index 51eadd8dc84..ec76ddfafde 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/ProjectLeakPeriodInfo.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/ProjectLeakPeriodInfo.tsx @@ -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>; } diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ProjectLeakPeriodInfo-test.tsx b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ProjectLeakPeriodInfo-test.tsx index fe635fe2114..516922ab183 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ProjectLeakPeriodInfo-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/ProjectLeakPeriodInfo-test.tsx @@ -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(); diff --git a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx index 86c20c726a5..43fe036e6be 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/LeakPeriodLegend.tsx @@ -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/BaselineSettingAnalysis.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx index f31cd308728..0528174c70c 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx @@ -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')} > diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx index 0f356bfd880..ef42354448d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx @@ -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')} > diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx index 5084aa5a124..e0ccc8487f9 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx @@ -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')})` : '') diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingReferenceBranch.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingReferenceBranch.tsx index 35600e31718..6604d1e39da 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingReferenceBranch.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingReferenceBranch.tsx @@ -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')} > diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx index a9236fe447e..0a3ec852329 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx index a3a5bf7f2dc..7d9943686e5 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx @@ -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 }, diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx index 9ed131753c9..ff07fee94dd 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchListRow.tsx @@ -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/App.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx index 80dbcf8f0eb..025cc21974e 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineApp.tsx @@ -36,6 +36,7 @@ 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'; @@ -66,11 +67,7 @@ interface State { const DEFAULT_NUMBER_OF_DAYS = '30'; -const DEFAULT_GENERAL_SETTING: { type: NewCodePeriodSettingType } = { - type: 'PREVIOUS_VERSION', -}; - -export class App extends React.PureComponent<Props, State> { +export class ProjectBaselineApp extends React.PureComponent<Props, State> { mounted = false; state: State = { branchList: [], @@ -107,7 +104,8 @@ export class App extends React.PureComponent<Props, State> { const { referenceBranch } = this.state; const defaultDays = - (generalSetting.type === 'NUMBER_OF_DAYS' && generalSetting.value) || DEFAULT_NUMBER_OF_DAYS; + (generalSetting.type === NewCodePeriodSettingType.NUMBER_OF_DAYS && generalSetting.value) || + DEFAULT_NUMBER_OF_DAYS; return { loading: false, @@ -116,10 +114,15 @@ export class App extends React.PureComponent<Props, State> { generalSetting, selected: currentSetting || generalSetting.type, overrideGeneralSetting: Boolean(currentSetting), - days: (currentSetting === 'NUMBER_OF_DAYS' && currentSettingValue) || defaultDays, - analysis: (currentSetting === 'SPECIFIC_ANALYSIS' && currentSettingValue) || '', + days: + (currentSetting === NewCodePeriodSettingType.NUMBER_OF_DAYS && currentSettingValue) || + defaultDays, + analysis: + (currentSetting === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && currentSettingValue) || + '', referenceBranch: - (currentSetting === 'REFERENCE_BRANCH' && currentSettingValue) || referenceBranch, + (currentSetting === NewCodePeriodSettingType.REFERENCE_BRANCH && currentSettingValue) || + referenceBranch, }; } @@ -143,10 +146,12 @@ export class App extends React.PureComponent<Props, State> { ([generalSetting, setting]) => { if (this.mounted) { if (!generalSetting.type) { - generalSetting = DEFAULT_GENERAL_SETTING; + generalSetting = { type: DEFAULT_GENERAL_SETTING_TYPE }; } const currentSettingValue = setting.value; - const currentSetting = setting.inherited ? undefined : setting.type || 'PREVIOUS_VERSION'; + const currentSetting = setting.inherited + ? undefined + : setting.type || DEFAULT_GENERAL_SETTING_TYPE; this.setState( this.getUpdatedState({ @@ -191,8 +196,11 @@ export class App extends React.PureComponent<Props, State> { handleCancel = () => this.setState( - ({ generalSetting = DEFAULT_GENERAL_SETTING, currentSetting, currentSettingValue }) => - this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue }) + ({ + generalSetting = { type: DEFAULT_GENERAL_SETTING_TYPE }, + currentSetting, + currentSettingValue, + }) => this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue }) ); handleSelectSetting = (selected?: NewCodePeriodSettingType) => this.setState({ selected }); @@ -322,4 +330,4 @@ export class App extends React.PureComponent<Props, State> { } } -export default withComponentContext(withAvailableFeatures(withAppStateContext(App))); +export default withComponentContext(withAvailableFeatures(withAppStateContext(ProjectBaselineApp))); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx index 76aa0647165..ac0eb18dbef 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx @@ -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__/BaselineSettingAnalysis-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingAnalysis-test.tsx index c4b9ec1718b..f6960dbe0fb 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingAnalysis-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingAnalysis-test.tsx @@ -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> = {}) { diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingDays-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingDays-test.tsx index 95fcbe423d4..a7aa306c6c6 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingDays-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingDays-test.tsx @@ -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', () => { diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingPreviousVersion-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingPreviousVersion-test.tsx index b680fb9cb88..9072f671c8d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingPreviousVersion-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingPreviousVersion-test.tsx @@ -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> = {}) { diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingReferenceBranch-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingReferenceBranch-test.tsx index c240643795d..dd9f28b5a76 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingReferenceBranch-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BaselineSettingReferenceBranch-test.tsx @@ -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', () => { diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchBaselineSettingModal-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchBaselineSettingModal-test.tsx index 06f71f16c33..8a0e83b8e50 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchBaselineSettingModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchBaselineSettingModal-test.tsx @@ -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', }); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchList-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchList-test.tsx index df935426e90..3fab1ff976f 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchList-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchList-test.tsx @@ -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} /> ); diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchListRow-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchListRow-test.tsx index 66f6593bb16..12f2bb83be1 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchListRow-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/BranchListRow-test.tsx @@ -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__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineApp-test.tsx index aaa7056fe0d..1f368a4060e 100644 --- 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__/ProjectBaselineApp-test.tsx @@ -28,7 +28,8 @@ import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers import { mockComponent } from '../../../../helpers/mocks/component'; import { mockAppState } from '../../../../helpers/testMocks'; import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils'; -import { App } from '../App'; +import { NewCodePeriodSettingType } from '../../../../types/types'; +import { ProjectBaselineApp } from '../ProjectBaselineApp'; jest.mock('../../../../api/newCodePeriod', () => ({ getNewCodePeriod: jest.fn().mockResolvedValue({}), @@ -75,12 +76,12 @@ it('should save correctly', async () => { const component = mockComponent(); const wrapper = shallowRender({ component }); await waitAndUpdate(wrapper); - wrapper.setState({ selected: 'NUMBER_OF_DAYS', days: '23' }); + wrapper.setState({ selected: NewCodePeriodSettingType.NUMBER_OF_DAYS, days: '23' }); wrapper.instance().handleSubmit(mockEvent()); await waitAndUpdate(wrapper); expect(setNewCodePeriod).toHaveBeenCalledWith({ project: component.key, - type: 'NUMBER_OF_DAYS', + type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '23', }); expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected')); @@ -92,7 +93,7 @@ it('should handle errors gracefully', async () => { (resetNewCodePeriod as jest.Mock).mockRejectedValue('error'); const wrapper = shallowRender(); - wrapper.setState({ selected: 'PREVIOUS_VERSION' }); + wrapper.setState({ selected: NewCodePeriodSettingType.PREVIOUS_VERSION }); await waitAndUpdate(wrapper); expect(wrapper.state('loading')).toBe(false); @@ -104,9 +105,9 @@ it('should handle errors gracefully', async () => { expect(wrapper.state('saving')).toBe(false); }); -function shallowRender(props: Partial<App['props']> = {}) { - return shallow<App>( - <App +function shallowRender(props: Partial<ProjectBaselineApp['props']> = {}) { + return shallow<ProjectBaselineApp>( + <ProjectBaselineApp branchLike={mockBranch()} branchLikes={[mockMainBranch()]} appState={mockAppState({ canAdmin: true })} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx index 09b2ecdfe1d..740f830be9c 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx @@ -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__/ProjectBaselineApp-test.tsx.snap index 847b62f98de..847b62f98de 100644 --- 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__/ProjectBaselineApp-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/utils-test.ts index 044e63b9da6..2d53227ef9f 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/utils-test.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { 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/settings/components/inputs/__tests__/InputForNumber-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/constants.ts index e22ba04f8c9..00c2c82e24b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForNumber-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/constants.ts @@ -17,26 +17,8 @@ * 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(); -}); +import { NewCodePeriodSettingType } from '../../types/types'; + +export const DEFAULT_GENERAL_SETTING_TYPE: NewCodePeriodSettingType = + NewCodePeriodSettingType.PREVIOUS_VERSION; diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx index 5397eae9553..2235fe7aabf 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/routes.tsx @@ -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; diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts b/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts index 3f249e2fd77..2ca20b85b1e 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts +++ b/server/sonar-web/src/main/js/apps/projectBaseline/utils.ts @@ -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 }; } diff --git a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx index c0167864efc..ca7d3221869 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/Definition.tsx @@ -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 }); diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx index d6c260352c6..d59c3bf8775 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx @@ -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 index 00000000000..a6df6000d50 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx @@ -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)}</>); +} diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx index 40869c50470..98d85ea341e 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/Definition-it.tsx @@ -17,156 +17,378 @@ * 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} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx index c95f2e18bb8..8d355a23ccf 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-it.tsx @@ -17,110 +17,76 @@ * 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 index 24a811bedac..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-it.tsx.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<ul - className="settings-sub-categories-list" -> - <li> - <ul - className="settings-definitions-list" - > - <li> - <div - className="settings-definition" - > - <div - className="settings-definition-left" - > - <h3 - className="settings-definition-name" - title="settings.new_code_period.title" - > - settings.new_code_period.title - </h3> - <div - className="small big-spacer-top" - > - <FormattedMessage - defaultMessage="settings.new_code_period.description" - id="settings.new_code_period.description" - values={ - { - "link": <withAppStateContext(DocLink) - to="/project-administration/defining-new-code/" - > - learn_more - </withAppStateContext(DocLink)>, - } - } - /> - <p - className="spacer-top" - > - settings.new_code_period.description2 - </p> - </div> - </div> - <div - className="settings-definition-right" - > - <DeferredSpinner /> - </div> - </div> - </li> - </ul> - </li> -</ul> -`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx index 1070420b649..463e2b9666d 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx @@ -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 () => { diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx index d752a6d397b..1c1643fcc00 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForBoolean.tsx @@ -18,19 +18,25 @@ * 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> ); diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForFormattedText.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForFormattedText.tsx index d2c57097c43..a1ef8a82097 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForFormattedText.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForFormattedText.tsx @@ -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} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx index 9cccb2c1d6f..15d45a9a159 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForJSON.tsx @@ -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>} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx index 6923d8f063e..558e0f93ba5 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSecured.tsx @@ -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)} diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx index c3e41c1bf8b..ec677d8d9da 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForSingleSelectList.tsx @@ -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)} /> diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.tsx index 0d4bb30b33d..cc81005adeb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/InputForText.tsx @@ -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)} /> ); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx index 80cb2eb7fd5..2fdffa0ebb9 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/MultiValueInput.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx index 0ebffee9f5d..39977561d40 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/inputs/PropertySetInput.tsx @@ -19,9 +19,11 @@ */ 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 index e575d170eb2..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/Input-test.tsx +++ /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 index f8f3aa5bb86..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForBoolean-test.tsx +++ /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 index 7412bb5479f..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForFormattedText-test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { 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 index d6a1c26e329..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForJSON-test.tsx +++ /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__/InputForPassword-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx deleted file mode 100644 index eba4748fe2a..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForPassword-test.tsx +++ /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 index a659d8cc9d0..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSecured-test.tsx +++ /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 index 0af1e4ee087..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForSingleSelectList-test.tsx +++ /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__/InputForText-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx deleted file mode 100644 index f9f2d2970cc..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForText-test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { 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 index 86d02a8a988..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/MultiValueInput-test.tsx +++ /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 index df337a9d136..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/PrimitiveInput-test.tsx +++ /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 index 577e0d439ab..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/SimpleInput-test.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { 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 index 3353ed9aa59..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/InputForJSON-test.tsx.snap +++ /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 index ddfa45a802d..00000000000 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/__snapshots__/PrimitiveInput-test.tsx.snap +++ /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", - ] - } -/> -`; diff --git a/server/sonar-web/src/main/js/components/controls/Toggle.tsx b/server/sonar-web/src/main/js/components/controls/Toggle.tsx index 9774b41a627..008c393d260 100644 --- a/server/sonar-web/src/main/js/components/controls/Toggle.tsx +++ b/server/sonar-web/src/main/js/components/controls/Toggle.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Toggle-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Toggle-test.tsx.snap index 8862993eb41..ac7aa845377 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Toggle-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Toggle-test.tsx.snap @@ -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 diff --git a/server/sonar-web/src/main/js/components/controls/buttons.tsx b/server/sonar-web/src/main/js/components/controls/buttons.tsx index 30e037e39b1..5137c5458fa 100644 --- a/server/sonar-web/src/main/js/components/controls/buttons.tsx +++ b/server/sonar-web/src/main/js/components/controls/buttons.tsx @@ -41,6 +41,7 @@ type AllowedButtonAttributes = Pick< | 'onMouseOver' | 'onMouseLeave' | 'tabIndex' + | 'role' >; interface ButtonProps extends AllowedButtonAttributes { diff --git a/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts index a3b58d10491..59e62a8bf8d 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { 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/apps/settings/components/inputs/__tests__/InputForString-test.tsx b/server/sonar-web/src/main/js/helpers/mocks/new-code-period.ts index b1ae445fe68..b33d62943f0 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/inputs/__tests__/InputForString-test.tsx +++ b/server/sonar-web/src/main/js/helpers/mocks/new-code-period.ts @@ -17,26 +17,12 @@ * 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(); -}); +import { NewCodePeriod, NewCodePeriodSettingType } from '../../types/types'; + +export function mockNewCodePeriod(overrides: Partial<NewCodePeriod> = {}) { + return { + type: NewCodePeriodSettingType.PREVIOUS_VERSION, + ...overrides, + }; +} diff --git a/server/sonar-web/src/main/js/helpers/mocks/settings.ts b/server/sonar-web/src/main/js/helpers/mocks/settings.ts index a43ee13ac2e..92e7588b44e 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/settings.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/settings.ts @@ -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', diff --git a/server/sonar-web/src/main/js/helpers/periods.ts b/server/sonar-web/src/main/js/helpers/periods.ts index 29434d4d2d1..376a61c2a9c 100644 --- a/server/sonar-web/src/main/js/helpers/periods.ts +++ b/server/sonar-web/src/main/js/helpers/periods.ts @@ -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; /* diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts index 819c1c0a08c..2412af05bf4 100644 --- a/server/sonar-web/src/main/js/types/settings.ts +++ b/server/sonar-web/src/main/js/types/settings.ts @@ -74,7 +74,6 @@ export interface SettingDefinition { } export interface SettingFieldDefinition extends SettingDefinition { - description: string; name: string; } diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts index 4a7bc7ab2e6..b921015846d 100644 --- a/server/sonar-web/src/main/js/types/types.ts +++ b/server/sonar-web/src/main/js/types/types.ts @@ -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; |