@@ -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)); | |||
} | |||
} |
@@ -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); |
@@ -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(); |
@@ -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; | |||
} | |||
@@ -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 |
@@ -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>; | |||
} |
@@ -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(); |
@@ -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)} |
@@ -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')} | |||
> |
@@ -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')} | |||
> |
@@ -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')})` : '') |
@@ -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')} | |||
> |
@@ -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} |
@@ -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 }, |
@@ -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) | |||
); | |||
} |
@@ -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))); |
@@ -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} |
@@ -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> = {}) { |
@@ -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', () => { |
@@ -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> = {}) { |
@@ -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', () => { |
@@ -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', | |||
}); |
@@ -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} | |||
/> | |||
); |
@@ -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, | |||
}); | |||
@@ -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 })} |
@@ -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, | |||
}); | |||
@@ -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, | |||
}) |
@@ -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; |
@@ -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; |
@@ -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 }; | |||
} |
@@ -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 }); |
@@ -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"> |
@@ -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)}</>); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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 />); | |||
} |
@@ -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> | |||
`; |
@@ -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 () => { |
@@ -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> | |||
); |
@@ -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} |
@@ -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>} |
@@ -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)} |
@@ -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)} | |||
/> |
@@ -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)} | |||
/> | |||
); | |||
} |
@@ -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> |
@@ -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)} | |||
/> |
@@ -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} />); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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(); | |||
}); |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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()} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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} | |||
/> | |||
); | |||
} |
@@ -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> | |||
`; |
@@ -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", | |||
] | |||
} | |||
/> | |||
`; |
@@ -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> |
@@ -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 |
@@ -41,6 +41,7 @@ type AllowedButtonAttributes = Pick< | |||
| 'onMouseOver' | |||
| 'onMouseLeave' | |||
| 'tabIndex' | |||
| 'role' | |||
>; | |||
interface ButtonProps extends AllowedButtonAttributes { |
@@ -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); | |||
}); | |||
}); |
@@ -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, | |||
}; | |||
} |
@@ -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', |
@@ -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; | |||
/* |
@@ -74,7 +74,6 @@ export interface SettingDefinition { | |||
} | |||
export interface SettingFieldDefinition extends SettingDefinition { | |||
description: string; | |||
name: string; | |||
} | |||
@@ -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; |
@@ -140,8 +140,6 @@ new_name=New name | |||
none=None | |||
no_tags=No tags | |||
not_now=Not now | |||
off=Off | |||
on=On | |||
or=Or | |||
open=Open | |||
optional=Optional | |||
@@ -244,7 +242,6 @@ no=No | |||
valid_input=Valid input | |||
#------------------------------------------------------------------------------ | |||
# | |||
# GENERIC EXPRESSIONS, sorted alphabetically | |||
@@ -1164,6 +1161,7 @@ settings.reset_confirm.title=Reset Setting | |||
settings.reset_confirm.description=Are you sure that you want to reset this setting? | |||
settings.definition.reset=Reset "{0}" to default values | |||
settings.definition.delete_value=Delete value "{1}" for setting "{0}" | |||
settings.definitions.delete_fields=Delete row {1} for setting "{0}" | |||
settings.search.placeholder=Find in Settings | |||
settings.search.results=Search results list |