--- /dev/null
+/*
+ * 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));
+ }
+}
* 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,
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({
subCategory: 'Announcement',
name: 'Announcement message',
description: 'Enter message',
- defaultValue: '.js,.jsx,.cjs,.vue,.mjs',
type: SettingType.TEXT,
}),
mockDefinition({
subCategory: 'Compute Engine',
name: 'Run analysis in paralel',
description: 'Enter message',
- defaultValue: '.js,.jsx,.cjs,.vue,.mjs',
type: SettingType.TEXT,
}),
mockDefinition({
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 {
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' }],
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 ?? '';
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);
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();
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;
</div>
);
- if (period.mode === 'days' || period.mode === 'NUMBER_OF_DAYS') {
+ if (period.mode === 'days' || period.mode === NewCodePeriodSettingType.NUMBER_OF_DAYS) {
return label;
}
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;
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
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;
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)
);
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>;
}
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', () => {
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();
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;
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)}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import classNames from 'classnames';
-import { debounce } from 'lodash';
-import * as React from 'react';
-import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
-import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import withAvailableFeatures, {
- WithAvailableFeaturesProps,
-} from '../../../app/components/available-features/withAvailableFeatures';
-import withComponentContext from '../../../app/components/componentContext/withComponentContext';
-import Suggestions from '../../../components/embed-docs-modal/Suggestions';
-import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
-import DeferredSpinner from '../../../components/ui/DeferredSpinner';
-import { isBranch, sortBranches } from '../../../helpers/branch-like';
-import { translate } from '../../../helpers/l10n';
-import { AppState } from '../../../types/appstate';
-import { Branch, BranchLike } from '../../../types/branch-like';
-import { Feature } from '../../../types/features';
-import { ParsedAnalysis } from '../../../types/project-activity';
-import { Component, NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
-import '../styles.css';
-import { getSettingValue } from '../utils';
-import AppHeader from './AppHeader';
-import BranchList from './BranchList';
-import ProjectBaselineSelector from './ProjectBaselineSelector';
-
-interface Props extends WithAvailableFeaturesProps {
- branchLike: Branch;
- branchLikes: BranchLike[];
- component: Component;
- appState: AppState;
-}
-
-interface State {
- analysis?: string;
- branchList: Branch[];
- currentSetting?: NewCodePeriodSettingType;
- currentSettingValue?: string;
- days: string;
- generalSetting?: NewCodePeriod;
- loading: boolean;
- overrideGeneralSetting?: boolean;
- referenceBranch?: string;
- saving: boolean;
- selected?: NewCodePeriodSettingType;
- success?: boolean;
-}
-
-const DEFAULT_NUMBER_OF_DAYS = '30';
-
-const DEFAULT_GENERAL_SETTING: { type: NewCodePeriodSettingType } = {
- type: 'PREVIOUS_VERSION',
-};
-
-export class App extends React.PureComponent<Props, State> {
- mounted = false;
- state: State = {
- branchList: [],
- days: DEFAULT_NUMBER_OF_DAYS,
- loading: true,
- saving: false,
- };
-
- // We use debounce as we could have multiple save in less that 3sec.
- resetSuccess = debounce(() => this.setState({ success: undefined }), 3000);
-
- componentDidMount() {
- this.mounted = true;
- this.fetchLeakPeriodSetting();
- this.sortAndFilterBranches(this.props.branchLikes);
- }
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.branchLikes !== this.props.branchLikes) {
- this.sortAndFilterBranches(this.props.branchLikes);
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- }
-
- getUpdatedState(params: {
- currentSetting?: NewCodePeriodSettingType;
- currentSettingValue?: string;
- generalSetting: NewCodePeriod;
- }) {
- const { currentSetting, currentSettingValue, generalSetting } = params;
- const { referenceBranch } = this.state;
-
- const defaultDays =
- (generalSetting.type === 'NUMBER_OF_DAYS' && generalSetting.value) || DEFAULT_NUMBER_OF_DAYS;
-
- return {
- loading: false,
- currentSetting,
- currentSettingValue,
- generalSetting,
- selected: currentSetting || generalSetting.type,
- overrideGeneralSetting: Boolean(currentSetting),
- days: (currentSetting === 'NUMBER_OF_DAYS' && currentSettingValue) || defaultDays,
- analysis: (currentSetting === 'SPECIFIC_ANALYSIS' && currentSettingValue) || '',
- referenceBranch:
- (currentSetting === 'REFERENCE_BRANCH' && currentSettingValue) || referenceBranch,
- };
- }
-
- sortAndFilterBranches(branchLikes: BranchLike[] = []) {
- const branchList = sortBranches(branchLikes.filter(isBranch));
- this.setState({ branchList, referenceBranch: branchList[0].name });
- }
-
- fetchLeakPeriodSetting() {
- const { branchLike, component } = this.props;
-
- this.setState({ loading: true });
-
- Promise.all([
- getNewCodePeriod(),
- getNewCodePeriod({
- branch: this.props.hasFeature(Feature.BranchSupport) ? undefined : branchLike.name,
- project: component.key,
- }),
- ]).then(
- ([generalSetting, setting]) => {
- if (this.mounted) {
- if (!generalSetting.type) {
- generalSetting = DEFAULT_GENERAL_SETTING;
- }
- const currentSettingValue = setting.value;
- const currentSetting = setting.inherited ? undefined : setting.type || 'PREVIOUS_VERSION';
-
- this.setState(
- this.getUpdatedState({
- generalSetting,
- currentSetting,
- currentSettingValue,
- })
- );
- }
- },
- () => {
- this.setState({ loading: false });
- }
- );
- }
-
- resetSetting = () => {
- this.setState({ saving: true });
- resetNewCodePeriod({ project: this.props.component.key }).then(
- () => {
- this.setState({
- saving: false,
- currentSetting: undefined,
- selected: undefined,
- success: true,
- });
- this.resetSuccess();
- },
- () => {
- this.setState({ saving: false });
- }
- );
- };
-
- handleSelectAnalysis = (analysis: ParsedAnalysis) => this.setState({ analysis: analysis.key });
-
- handleSelectDays = (days: string) => this.setState({ days });
-
- handleSelectReferenceBranch = (referenceBranch: string) => {
- this.setState({ referenceBranch });
- };
-
- handleCancel = () =>
- this.setState(
- ({ generalSetting = DEFAULT_GENERAL_SETTING, currentSetting, currentSettingValue }) =>
- this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue })
- );
-
- handleSelectSetting = (selected?: NewCodePeriodSettingType) => this.setState({ selected });
-
- handleToggleSpecificSetting = (overrideGeneralSetting: boolean) =>
- this.setState({ overrideGeneralSetting });
-
- handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
- e.preventDefault();
-
- const { component } = this.props;
- const { analysis, days, selected: type, referenceBranch, overrideGeneralSetting } = this.state;
-
- if (!overrideGeneralSetting) {
- this.resetSetting();
- return;
- }
-
- const value = getSettingValue({ type, analysis, days, referenceBranch });
-
- if (type) {
- this.setState({ saving: true });
- setNewCodePeriod({
- project: component.key,
- type,
- value,
- }).then(
- () => {
- this.setState({
- saving: false,
- currentSetting: type,
- currentSettingValue: value || undefined,
- success: true,
- });
- this.resetSuccess();
- },
- () => {
- this.setState({ saving: false });
- }
- );
- }
- };
-
- render() {
- const { appState, component, branchLike } = this.props;
- const {
- analysis,
- branchList,
- currentSetting,
- days,
- generalSetting,
- loading,
- currentSettingValue,
- overrideGeneralSetting,
- referenceBranch,
- saving,
- selected,
- success,
- } = this.state;
- const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
-
- return (
- <>
- <Suggestions suggestions="project_baseline" />
- <div className="page page-limited">
- <AppHeader canAdmin={!!appState.canAdmin} />
- {loading ? (
- <DeferredSpinner />
- ) : (
- <div className="panel-white project-baseline">
- {branchSupportEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
-
- {generalSetting && overrideGeneralSetting !== undefined && (
- <ProjectBaselineSelector
- analysis={analysis}
- branch={branchLike}
- branchList={branchList}
- branchesEnabled={branchSupportEnabled}
- component={component.key}
- currentSetting={currentSetting}
- currentSettingValue={currentSettingValue}
- days={days}
- generalSetting={generalSetting}
- onCancel={this.handleCancel}
- onSelectAnalysis={this.handleSelectAnalysis}
- onSelectDays={this.handleSelectDays}
- onSelectReferenceBranch={this.handleSelectReferenceBranch}
- onSelectSetting={this.handleSelectSetting}
- onSubmit={this.handleSubmit}
- onToggleSpecificSetting={this.handleToggleSpecificSetting}
- overrideGeneralSetting={overrideGeneralSetting}
- referenceBranch={referenceBranch}
- saving={saving}
- selected={selected}
- />
- )}
-
- <div className={classNames('spacer-top', { invisible: saving || !success })}>
- <span className="text-success">
- <AlertSuccessIcon className="spacer-right" />
- {translate('settings.state.saved')}
- </span>
- </div>
- {generalSetting && branchSupportEnabled && (
- <div className="huge-spacer-top branch-baseline-selector">
- <hr />
- <h2>{translate('project_baseline.configure_branches')}</h2>
- <BranchList
- branchList={branchList}
- component={component}
- inheritedSetting={
- currentSetting
- ? {
- type: currentSetting,
- value: currentSettingValue,
- }
- : generalSetting
- }
- />
- </div>
- )}
- </div>
- )}
- </div>
- </>
- );
- }
-}
-
-export default withComponentContext(withAvailableFeatures(withAppStateContext(App)));
return (
<RadioCard
disabled={disabled}
- onClick={() => onSelect('SPECIFIC_ANALYSIS')}
+ onClick={() => onSelect(NewCodePeriodSettingType.SPECIFIC_ANALYSIS)}
selected={selected}
title={translate('baseline.specific_analysis')}
>
<RadioCard
className={className}
disabled={disabled}
- onClick={() => onSelect('NUMBER_OF_DAYS')}
+ onClick={() => onSelect(NewCodePeriodSettingType.NUMBER_OF_DAYS)}
selected={selected}
title={translate('baseline.number_days')}
>
return (
<RadioCard
disabled={disabled}
- onClick={() => onSelect('PREVIOUS_VERSION')}
+ onClick={() => onSelect(NewCodePeriodSettingType.PREVIOUS_VERSION)}
selected={selected}
title={
translate('baseline.previous_version') + (isDefault ? ` (${translate('default')})` : '')
<RadioCard
className={className}
disabled={disabled}
- onClick={() => props.onSelect('REFERENCE_BRANCH')}
+ onClick={() => props.onSelect(NewCodePeriodSettingType.REFERENCE_BRANCH)}
selected={selected}
title={translate('baseline.reference_branch')}
>
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,
};
<BaselineSettingPreviousVersion
isDefault={false}
onSelect={this.handleSelectSetting}
- selected={selected === 'PREVIOUS_VERSION'}
+ selected={selected === NewCodePeriodSettingType.PREVIOUS_VERSION}
/>
<BaselineSettingDays
days={days}
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}
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';
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 },
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;
function renderNewCodePeriodSetting(newCodePeriod: NewCodePeriod) {
switch (newCodePeriod.type) {
- case 'SPECIFIC_ANALYSIS':
+ case NewCodePeriodSettingType.SPECIFIC_ANALYSIS:
return (
<>
{`${translate('baseline.specific_analysis')}: `}
)}
</>
);
- 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;
) {
return (
!branch.newCodePeriod &&
- inheritedSetting.type === 'REFERENCE_BRANCH' &&
+ inheritedSetting.type === NewCodePeriodSettingType.REFERENCE_BRANCH &&
branch.name === inheritedSetting.value
);
}
return (
branch.newCodePeriod &&
branch.newCodePeriod.value &&
- branch.newCodePeriod.type === 'REFERENCE_BRANCH' &&
+ branch.newCodePeriod.type === NewCodePeriodSettingType.REFERENCE_BRANCH &&
!existingBranches.includes(branch.newCodePeriod.value)
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import classNames from 'classnames';
+import { debounce } from 'lodash';
+import * as React from 'react';
+import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
+import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
+import withAvailableFeatures, {
+ WithAvailableFeaturesProps,
+} from '../../../app/components/available-features/withAvailableFeatures';
+import withComponentContext from '../../../app/components/componentContext/withComponentContext';
+import Suggestions from '../../../components/embed-docs-modal/Suggestions';
+import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
+import DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { isBranch, sortBranches } from '../../../helpers/branch-like';
+import { translate } from '../../../helpers/l10n';
+import { AppState } from '../../../types/appstate';
+import { Branch, BranchLike } from '../../../types/branch-like';
+import { Feature } from '../../../types/features';
+import { ParsedAnalysis } from '../../../types/project-activity';
+import { Component, NewCodePeriod, NewCodePeriodSettingType } from '../../../types/types';
+import { DEFAULT_GENERAL_SETTING_TYPE } from '../constants';
+import '../styles.css';
+import { getSettingValue } from '../utils';
+import AppHeader from './AppHeader';
+import BranchList from './BranchList';
+import ProjectBaselineSelector from './ProjectBaselineSelector';
+
+interface Props extends WithAvailableFeaturesProps {
+ branchLike: Branch;
+ branchLikes: BranchLike[];
+ component: Component;
+ appState: AppState;
+}
+
+interface State {
+ analysis?: string;
+ branchList: Branch[];
+ currentSetting?: NewCodePeriodSettingType;
+ currentSettingValue?: string;
+ days: string;
+ generalSetting?: NewCodePeriod;
+ loading: boolean;
+ overrideGeneralSetting?: boolean;
+ referenceBranch?: string;
+ saving: boolean;
+ selected?: NewCodePeriodSettingType;
+ success?: boolean;
+}
+
+const DEFAULT_NUMBER_OF_DAYS = '30';
+
+export class ProjectBaselineApp extends React.PureComponent<Props, State> {
+ mounted = false;
+ state: State = {
+ branchList: [],
+ days: DEFAULT_NUMBER_OF_DAYS,
+ loading: true,
+ saving: false,
+ };
+
+ // We use debounce as we could have multiple save in less that 3sec.
+ resetSuccess = debounce(() => this.setState({ success: undefined }), 3000);
+
+ componentDidMount() {
+ this.mounted = true;
+ this.fetchLeakPeriodSetting();
+ this.sortAndFilterBranches(this.props.branchLikes);
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.branchLikes !== this.props.branchLikes) {
+ this.sortAndFilterBranches(this.props.branchLikes);
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ getUpdatedState(params: {
+ currentSetting?: NewCodePeriodSettingType;
+ currentSettingValue?: string;
+ generalSetting: NewCodePeriod;
+ }) {
+ const { currentSetting, currentSettingValue, generalSetting } = params;
+ const { referenceBranch } = this.state;
+
+ const defaultDays =
+ (generalSetting.type === NewCodePeriodSettingType.NUMBER_OF_DAYS && generalSetting.value) ||
+ DEFAULT_NUMBER_OF_DAYS;
+
+ return {
+ loading: false,
+ currentSetting,
+ currentSettingValue,
+ generalSetting,
+ selected: currentSetting || generalSetting.type,
+ overrideGeneralSetting: Boolean(currentSetting),
+ days:
+ (currentSetting === NewCodePeriodSettingType.NUMBER_OF_DAYS && currentSettingValue) ||
+ defaultDays,
+ analysis:
+ (currentSetting === NewCodePeriodSettingType.SPECIFIC_ANALYSIS && currentSettingValue) ||
+ '',
+ referenceBranch:
+ (currentSetting === NewCodePeriodSettingType.REFERENCE_BRANCH && currentSettingValue) ||
+ referenceBranch,
+ };
+ }
+
+ sortAndFilterBranches(branchLikes: BranchLike[] = []) {
+ const branchList = sortBranches(branchLikes.filter(isBranch));
+ this.setState({ branchList, referenceBranch: branchList[0].name });
+ }
+
+ fetchLeakPeriodSetting() {
+ const { branchLike, component } = this.props;
+
+ this.setState({ loading: true });
+
+ Promise.all([
+ getNewCodePeriod(),
+ getNewCodePeriod({
+ branch: this.props.hasFeature(Feature.BranchSupport) ? undefined : branchLike.name,
+ project: component.key,
+ }),
+ ]).then(
+ ([generalSetting, setting]) => {
+ if (this.mounted) {
+ if (!generalSetting.type) {
+ generalSetting = { type: DEFAULT_GENERAL_SETTING_TYPE };
+ }
+ const currentSettingValue = setting.value;
+ const currentSetting = setting.inherited
+ ? undefined
+ : setting.type || DEFAULT_GENERAL_SETTING_TYPE;
+
+ this.setState(
+ this.getUpdatedState({
+ generalSetting,
+ currentSetting,
+ currentSettingValue,
+ })
+ );
+ }
+ },
+ () => {
+ this.setState({ loading: false });
+ }
+ );
+ }
+
+ resetSetting = () => {
+ this.setState({ saving: true });
+ resetNewCodePeriod({ project: this.props.component.key }).then(
+ () => {
+ this.setState({
+ saving: false,
+ currentSetting: undefined,
+ selected: undefined,
+ success: true,
+ });
+ this.resetSuccess();
+ },
+ () => {
+ this.setState({ saving: false });
+ }
+ );
+ };
+
+ handleSelectAnalysis = (analysis: ParsedAnalysis) => this.setState({ analysis: analysis.key });
+
+ handleSelectDays = (days: string) => this.setState({ days });
+
+ handleSelectReferenceBranch = (referenceBranch: string) => {
+ this.setState({ referenceBranch });
+ };
+
+ handleCancel = () =>
+ this.setState(
+ ({
+ generalSetting = { type: DEFAULT_GENERAL_SETTING_TYPE },
+ currentSetting,
+ currentSettingValue,
+ }) => this.getUpdatedState({ generalSetting, currentSetting, currentSettingValue })
+ );
+
+ handleSelectSetting = (selected?: NewCodePeriodSettingType) => this.setState({ selected });
+
+ handleToggleSpecificSetting = (overrideGeneralSetting: boolean) =>
+ this.setState({ overrideGeneralSetting });
+
+ handleSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
+ e.preventDefault();
+
+ const { component } = this.props;
+ const { analysis, days, selected: type, referenceBranch, overrideGeneralSetting } = this.state;
+
+ if (!overrideGeneralSetting) {
+ this.resetSetting();
+ return;
+ }
+
+ const value = getSettingValue({ type, analysis, days, referenceBranch });
+
+ if (type) {
+ this.setState({ saving: true });
+ setNewCodePeriod({
+ project: component.key,
+ type,
+ value,
+ }).then(
+ () => {
+ this.setState({
+ saving: false,
+ currentSetting: type,
+ currentSettingValue: value || undefined,
+ success: true,
+ });
+ this.resetSuccess();
+ },
+ () => {
+ this.setState({ saving: false });
+ }
+ );
+ }
+ };
+
+ render() {
+ const { appState, component, branchLike } = this.props;
+ const {
+ analysis,
+ branchList,
+ currentSetting,
+ days,
+ generalSetting,
+ loading,
+ currentSettingValue,
+ overrideGeneralSetting,
+ referenceBranch,
+ saving,
+ selected,
+ success,
+ } = this.state;
+ const branchSupportEnabled = this.props.hasFeature(Feature.BranchSupport);
+
+ return (
+ <>
+ <Suggestions suggestions="project_baseline" />
+ <div className="page page-limited">
+ <AppHeader canAdmin={!!appState.canAdmin} />
+ {loading ? (
+ <DeferredSpinner />
+ ) : (
+ <div className="panel-white project-baseline">
+ {branchSupportEnabled && <h2>{translate('project_baseline.default_setting')}</h2>}
+
+ {generalSetting && overrideGeneralSetting !== undefined && (
+ <ProjectBaselineSelector
+ analysis={analysis}
+ branch={branchLike}
+ branchList={branchList}
+ branchesEnabled={branchSupportEnabled}
+ component={component.key}
+ currentSetting={currentSetting}
+ currentSettingValue={currentSettingValue}
+ days={days}
+ generalSetting={generalSetting}
+ onCancel={this.handleCancel}
+ onSelectAnalysis={this.handleSelectAnalysis}
+ onSelectDays={this.handleSelectDays}
+ onSelectReferenceBranch={this.handleSelectReferenceBranch}
+ onSelectSetting={this.handleSelectSetting}
+ onSubmit={this.handleSubmit}
+ onToggleSpecificSetting={this.handleToggleSpecificSetting}
+ overrideGeneralSetting={overrideGeneralSetting}
+ referenceBranch={referenceBranch}
+ saving={saving}
+ selected={selected}
+ />
+ )}
+
+ <div className={classNames('spacer-top', { invisible: saving || !success })}>
+ <span className="text-success">
+ <AlertSuccessIcon className="spacer-right" />
+ {translate('settings.state.saved')}
+ </span>
+ </div>
+ {generalSetting && branchSupportEnabled && (
+ <div className="huge-spacer-top branch-baseline-selector">
+ <hr />
+ <h2>{translate('project_baseline.configure_branches')}</h2>
+ <BranchList
+ branchList={branchList}
+ component={component}
+ inheritedSetting={
+ currentSetting
+ ? {
+ type: currentSetting,
+ value: currentSettingValue,
+ }
+ : generalSetting
+ }
+ />
+ </div>
+ )}
+ </div>
+ )}
+ </div>
+ </>
+ );
+ }
+}
+
+export default withComponentContext(withAvailableFeatures(withAppStateContext(ProjectBaselineApp)));
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 || '?'
<BaselineSettingPreviousVersion
disabled={!overrideGeneralSetting}
onSelect={props.onSelectSetting}
- selected={overrideGeneralSetting && selected === 'PREVIOUS_VERSION'}
+ selected={
+ overrideGeneralSetting && selected === NewCodePeriodSettingType.PREVIOUS_VERSION
+ }
/>
<BaselineSettingDays
days={days}
isValid={isValid}
onChangeDays={props.onSelectDays}
onSelect={props.onSelectSetting}
- selected={overrideGeneralSetting && selected === 'NUMBER_OF_DAYS'}
+ selected={
+ overrideGeneralSetting && selected === NewCodePeriodSettingType.NUMBER_OF_DAYS
+ }
/>
{branchesEnabled ? (
<BaselineSettingReferenceBranch
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}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import {
- getNewCodePeriod,
- resetNewCodePeriod,
- setNewCodePeriod,
-} from '../../../../api/newCodePeriod';
-import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import { mockAppState } from '../../../../helpers/testMocks';
-import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
-import { App } from '../App';
-
-jest.mock('../../../../api/newCodePeriod', () => ({
- getNewCodePeriod: jest.fn().mockResolvedValue({}),
- resetNewCodePeriod: jest.fn().mockResolvedValue({}),
- setNewCodePeriod: jest.fn().mockResolvedValue({}),
-}));
-
-it('should render correctly', async () => {
- let wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-
- wrapper = shallowRender({ appState: mockAppState({ canAdmin: true }), hasFeature: () => false });
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot('without branch support');
-});
-
-it('should initialize correctly', async () => {
- const wrapper = shallowRender({
- branchLikes: [mockBranch(), mockPullRequest(), mockMainBranch()],
- });
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state().branchList).toHaveLength(2);
- expect(wrapper.state().referenceBranch).toBe('master');
-});
-
-it('should not display reset button if project setting is not set', () => {
- const wrapper = shallowRender();
-
- expect(wrapper.find('Button')).toHaveLength(0);
-});
-
-it('should reset the setting correctly', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- wrapper.instance().resetSetting();
- await waitAndUpdate(wrapper);
- expect(wrapper.state('currentSetting')).toBeUndefined();
- expect(wrapper.state('selected')).toBeUndefined();
-});
-
-it('should save correctly', async () => {
- const component = mockComponent();
- const wrapper = shallowRender({ component });
- await waitAndUpdate(wrapper);
- wrapper.setState({ selected: 'NUMBER_OF_DAYS', days: '23' });
- wrapper.instance().handleSubmit(mockEvent());
- await waitAndUpdate(wrapper);
- expect(setNewCodePeriod).toHaveBeenCalledWith({
- project: component.key,
- type: 'NUMBER_OF_DAYS',
- value: '23',
- });
- expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
-});
-
-it('should handle errors gracefully', async () => {
- (getNewCodePeriod as jest.Mock).mockRejectedValue('error');
- (setNewCodePeriod as jest.Mock).mockRejectedValue('error');
- (resetNewCodePeriod as jest.Mock).mockRejectedValue('error');
-
- const wrapper = shallowRender();
- wrapper.setState({ selected: 'PREVIOUS_VERSION' });
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state('loading')).toBe(false);
- wrapper.instance().resetSetting();
- await waitAndUpdate(wrapper);
- expect(wrapper.state('saving')).toBe(false);
- wrapper.instance().handleSubmit(mockEvent());
- await waitAndUpdate(wrapper);
- expect(wrapper.state('saving')).toBe(false);
-});
-
-function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(
- <App
- branchLike={mockBranch()}
- branchLikes={[mockMainBranch()]}
- appState={mockAppState({ canAdmin: true })}
- hasFeature={jest.fn().mockReturnValue(true)}
- component={mockComponent()}
- {...props}
- />
- );
-}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { NewCodePeriodSettingType } from '../../../../types/types';
import BaselineSettingAnalysis, { Props } from '../BaselineSettingAnalysis';
it('should render correctly', () => {
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> = {}) {
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { NewCodePeriodSettingType } from '../../../../types/types';
import BaselineSettingDays, { Props } from '../BaselineSettingDays';
it('should render correctly', () => {
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', () => {
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { NewCodePeriodSettingType } from '../../../../types/types';
import BaselineSettingPreviousVersion, { Props } from '../BaselineSettingPreviousVersion';
it('should render correctly', () => {
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> = {}) {
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,
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', () => {
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', () => ({
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);
});
component,
});
- wrapper.setState({ analysis: 'analysis572893', selected: 'SPECIFIC_ANALYSIS' });
+ wrapper.setState({
+ analysis: 'analysis572893',
+ selected: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
+ });
await waitAndUpdate(wrapper);
wrapper.instance().handleSubmit(mockEvent());
expect(setNewCodePeriod).toHaveBeenCalledWith({
project: component,
- type: 'SPECIFIC_ANALYSIS',
+ type: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
value: 'analysis572893',
branch: 'branchname',
});
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';
{
projectKey: '',
branchKey: 'master',
- type: 'NUMBER_OF_DAYS',
+ type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
value: '27',
},
];
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({
isMain: true,
name: 'master',
newCodePeriod: {
- type: 'NUMBER_OF_DAYS',
+ type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
value: '23',
},
});
<BranchList
branchList={[]}
component={mockComponent()}
- inheritedSetting={{ type: 'PREVIOUS_VERSION' }}
+ inheritedSetting={{ type: NewCodePeriodSettingType.PREVIOUS_VERSION }}
{...props}
/>
);
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', () => {
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(
branch: {
...mockBranch(),
newCodePeriod: {
- type: 'SPECIFIC_ANALYSIS',
+ type: NewCodePeriodSettingType.SPECIFIC_ANALYSIS,
value: 'A85835',
effectiveValue: '2018-12-02T13:01:12',
},
).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');
});
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,
});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import {
+ getNewCodePeriod,
+ resetNewCodePeriod,
+ setNewCodePeriod,
+} from '../../../../api/newCodePeriod';
+import { mockBranch, mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockAppState } from '../../../../helpers/testMocks';
+import { mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
+import { NewCodePeriodSettingType } from '../../../../types/types';
+import { ProjectBaselineApp } from '../ProjectBaselineApp';
+
+jest.mock('../../../../api/newCodePeriod', () => ({
+ getNewCodePeriod: jest.fn().mockResolvedValue({}),
+ resetNewCodePeriod: jest.fn().mockResolvedValue({}),
+ setNewCodePeriod: jest.fn().mockResolvedValue({}),
+}));
+
+it('should render correctly', async () => {
+ let wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper = shallowRender({ appState: mockAppState({ canAdmin: true }), hasFeature: () => false });
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot('without branch support');
+});
+
+it('should initialize correctly', async () => {
+ const wrapper = shallowRender({
+ branchLikes: [mockBranch(), mockPullRequest(), mockMainBranch()],
+ });
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state().branchList).toHaveLength(2);
+ expect(wrapper.state().referenceBranch).toBe('master');
+});
+
+it('should not display reset button if project setting is not set', () => {
+ const wrapper = shallowRender();
+
+ expect(wrapper.find('Button')).toHaveLength(0);
+});
+
+it('should reset the setting correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ wrapper.instance().resetSetting();
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('currentSetting')).toBeUndefined();
+ expect(wrapper.state('selected')).toBeUndefined();
+});
+
+it('should save correctly', async () => {
+ const component = mockComponent();
+ const wrapper = shallowRender({ component });
+ await waitAndUpdate(wrapper);
+ wrapper.setState({ selected: NewCodePeriodSettingType.NUMBER_OF_DAYS, days: '23' });
+ wrapper.instance().handleSubmit(mockEvent());
+ await waitAndUpdate(wrapper);
+ expect(setNewCodePeriod).toHaveBeenCalledWith({
+ project: component.key,
+ type: NewCodePeriodSettingType.NUMBER_OF_DAYS,
+ value: '23',
+ });
+ expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
+});
+
+it('should handle errors gracefully', async () => {
+ (getNewCodePeriod as jest.Mock).mockRejectedValue('error');
+ (setNewCodePeriod as jest.Mock).mockRejectedValue('error');
+ (resetNewCodePeriod as jest.Mock).mockRejectedValue('error');
+
+ const wrapper = shallowRender();
+ wrapper.setState({ selected: NewCodePeriodSettingType.PREVIOUS_VERSION });
+ await waitAndUpdate(wrapper);
+
+ expect(wrapper.state('loading')).toBe(false);
+ wrapper.instance().resetSetting();
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('saving')).toBe(false);
+ wrapper.instance().handleSubmit(mockEvent());
+ await waitAndUpdate(wrapper);
+ expect(wrapper.state('saving')).toBe(false);
+});
+
+function shallowRender(props: Partial<ProjectBaselineApp['props']> = {}) {
+ return shallow<ProjectBaselineApp>(
+ <ProjectBaselineApp
+ branchLike={mockBranch()}
+ branchLikes={[mockMainBranch()]}
+ appState={mockAppState({ canAdmin: true })}
+ hasFeature={jest.fn().mockReturnValue(true)}
+ component={mockComponent()}
+ {...props}
+ />
+ );
+}
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', () => {
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);
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);
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);
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,
});
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,
});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<Fragment>
- <Suggestions
- suggestions="project_baseline"
- />
- <div
- className="page page-limited"
- >
- <AppHeader
- canAdmin={true}
- />
- <div
- className="panel-white project-baseline"
- >
- <h2>
- project_baseline.default_setting
- </h2>
- <ProjectBaselineSelector
- analysis=""
- branch={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-6.7",
- }
- }
- branchList={
- [
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- ]
- }
- branchesEnabled={true}
- component="my-project"
- currentSetting="PREVIOUS_VERSION"
- days="30"
- generalSetting={
- {
- "type": "PREVIOUS_VERSION",
- }
- }
- onCancel={[Function]}
- onSelectAnalysis={[Function]}
- onSelectDays={[Function]}
- onSelectReferenceBranch={[Function]}
- onSelectSetting={[Function]}
- onSubmit={[Function]}
- onToggleSpecificSetting={[Function]}
- overrideGeneralSetting={true}
- referenceBranch="master"
- saving={false}
- selected="PREVIOUS_VERSION"
- />
- <div
- className="spacer-top invisible"
- >
- <span
- className="text-success"
- >
- <AlertSuccessIcon
- className="spacer-right"
- />
- settings.state.saved
- </span>
- </div>
- <div
- className="huge-spacer-top branch-baseline-selector"
- >
- <hr />
- <h2>
- project_baseline.configure_branches
- </h2>
- <BranchList
- branchList={
- [
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- ]
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- inheritedSetting={
- {
- "type": "PREVIOUS_VERSION",
- "value": undefined,
- }
- }
- />
- </div>
- </div>
- </div>
-</Fragment>
-`;
-
-exports[`should render correctly: without branch support 1`] = `
-<Fragment>
- <Suggestions
- suggestions="project_baseline"
- />
- <div
- className="page page-limited"
- >
- <AppHeader
- canAdmin={true}
- />
- <div
- className="panel-white project-baseline"
- >
- <ProjectBaselineSelector
- analysis=""
- branch={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-6.7",
- }
- }
- branchList={
- [
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- ]
- }
- branchesEnabled={false}
- component="my-project"
- currentSetting="PREVIOUS_VERSION"
- days="30"
- generalSetting={
- {
- "type": "PREVIOUS_VERSION",
- }
- }
- onCancel={[Function]}
- onSelectAnalysis={[Function]}
- onSelectDays={[Function]}
- onSelectReferenceBranch={[Function]}
- onSelectSetting={[Function]}
- onSubmit={[Function]}
- onToggleSpecificSetting={[Function]}
- overrideGeneralSetting={true}
- referenceBranch="master"
- saving={false}
- selected="PREVIOUS_VERSION"
- />
- <div
- className="spacer-top invisible"
- >
- <span
- className="text-success"
- >
- <AlertSuccessIcon
- className="spacer-right"
- />
- settings.state.saved
- </span>
- </div>
- </div>
- </div>
-</Fragment>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<Fragment>
+ <Suggestions
+ suggestions="project_baseline"
+ />
+ <div
+ className="page page-limited"
+ >
+ <AppHeader
+ canAdmin={true}
+ />
+ <div
+ className="panel-white project-baseline"
+ >
+ <h2>
+ project_baseline.default_setting
+ </h2>
+ <ProjectBaselineSelector
+ analysis=""
+ branch={
+ {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
+ branchList={
+ [
+ {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ },
+ ]
+ }
+ branchesEnabled={true}
+ component="my-project"
+ currentSetting="PREVIOUS_VERSION"
+ days="30"
+ generalSetting={
+ {
+ "type": "PREVIOUS_VERSION",
+ }
+ }
+ onCancel={[Function]}
+ onSelectAnalysis={[Function]}
+ onSelectDays={[Function]}
+ onSelectReferenceBranch={[Function]}
+ onSelectSetting={[Function]}
+ onSubmit={[Function]}
+ onToggleSpecificSetting={[Function]}
+ overrideGeneralSetting={true}
+ referenceBranch="master"
+ saving={false}
+ selected="PREVIOUS_VERSION"
+ />
+ <div
+ className="spacer-top invisible"
+ >
+ <span
+ className="text-success"
+ >
+ <AlertSuccessIcon
+ className="spacer-right"
+ />
+ settings.state.saved
+ </span>
+ </div>
+ <div
+ className="huge-spacer-top branch-baseline-selector"
+ >
+ <hr />
+ <h2>
+ project_baseline.configure_branches
+ </h2>
+ <BranchList
+ branchList={
+ [
+ {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ },
+ ]
+ }
+ component={
+ {
+ "breadcrumbs": [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": [
+ {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": [],
+ }
+ }
+ inheritedSetting={
+ {
+ "type": "PREVIOUS_VERSION",
+ "value": undefined,
+ }
+ }
+ />
+ </div>
+ </div>
+ </div>
+</Fragment>
+`;
+
+exports[`should render correctly: without branch support 1`] = `
+<Fragment>
+ <Suggestions
+ suggestions="project_baseline"
+ />
+ <div
+ className="page page-limited"
+ >
+ <AppHeader
+ canAdmin={true}
+ />
+ <div
+ className="panel-white project-baseline"
+ >
+ <ProjectBaselineSelector
+ analysis=""
+ branch={
+ {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-6.7",
+ }
+ }
+ branchList={
+ [
+ {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ },
+ ]
+ }
+ branchesEnabled={false}
+ component="my-project"
+ currentSetting="PREVIOUS_VERSION"
+ days="30"
+ generalSetting={
+ {
+ "type": "PREVIOUS_VERSION",
+ }
+ }
+ onCancel={[Function]}
+ onSelectAnalysis={[Function]}
+ onSelectDays={[Function]}
+ onSelectReferenceBranch={[Function]}
+ onSelectSetting={[Function]}
+ onSubmit={[Function]}
+ onToggleSpecificSetting={[Function]}
+ overrideGeneralSetting={true}
+ referenceBranch="master"
+ saving={false}
+ selected="PREVIOUS_VERSION"
+ />
+ <div
+ className="spacer-top invisible"
+ >
+ <span
+ className="text-success"
+ >
+ <AlertSuccessIcon
+ className="spacer-right"
+ />
+ settings.state.saved
+ </span>
+ </div>
+ </div>
+ </div>
+</Fragment>
+`;
* 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', () => {
};
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
+ );
});
});
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 });
});
});
expect(
validateSetting({
- currentSetting: 'PREVIOUS_VERSION',
+ currentSetting: NewCodePeriodSettingType.PREVIOUS_VERSION,
days: '',
overrideGeneralSetting: false,
})
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { NewCodePeriodSettingType } from '../../types/types';
+
+export const DEFAULT_GENERAL_SETTING_TYPE: NewCodePeriodSettingType =
+ NewCodePeriodSettingType.PREVIOUS_VERSION;
*/
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;
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;
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 };
}
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 });
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 });
success: boolean;
}
-const DEFAULT_SETTING = 'PREVIOUS_VERSION';
-
export default class NewCodePeriod extends React.PureComponent<{}, State> {
mounted = false;
state: 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(() => {
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,
}));
};
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() {
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">
<BaselineSettingPreviousVersion
isDefault={true}
onSelect={this.onSelectSetting}
- selected={selected === 'PREVIOUS_VERSION'}
+ selected={selected === NewCodePeriodSettingType.PREVIOUS_VERSION}
/>
<BaselineSettingDays
className="spacer-top"
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">
--- /dev/null
+/*
+ * 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)}</>);
+}
* 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}
+ />
+ );
}
* 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 />);
}
+++ /dev/null
-// 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>
-`;
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' }),
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();
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 () => {
* 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>
);
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;
{editMode ? (
<div className="display-flex-row">
<textarea
+ aria-label={getPropertyName(setting.definition)}
className="settings-large-input text-top spacer-right"
name={name}
onChange={handleInputChange}
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;
};
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>}
import {
DefaultInputProps,
DefaultSpecializedInputProps,
+ getPropertyName,
getUniqueName,
isDefaultOrInherited,
} from '../../utils';
<>
<input className="hidden" type="password" />
<Input
+ aria-label={getPropertyName(setting.definition)}
autoComplete="off"
className="js-setting-input settings-large-input"
isDefault={isDefaultOrInherited(setting)}
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'>;
};
render() {
- const { options: opts, name, value } = this.props;
+ const { options: opts, name, value, setting } = this.props;
const options = opts.map((option) => ({
label: option,
className="settings-large-input"
name={name}
onChange={this.handleInputChange}
+ aria-label={getPropertyName(setting.definition)}
options={options}
value={options.find((option) => option.value === value)}
/>
* 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>) => {
};
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)}
/>
);
}
render() {
const displayedValue = [...this.ensureValue(), ...getEmptyValue(this.props.setting.definition)];
+
return (
<div>
<ul>
*/
import * as React from 'react';
import { DeleteButton } from '../../../../components/controls/buttons';
+import { translateWithParameters } from '../../../../helpers/l10n';
import {
DefaultSpecializedInputProps,
getEmptyValue,
+ getPropertyName,
getUniqueName,
isCategoryDefinition,
} from '../../utils';
<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)}
/>
+++ /dev/null
-/*
- * 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} />);
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockSetting } from '../../../../../helpers/mocks/settings';
-import InputForNumber from '../InputForNumber';
-import SimpleInput from '../SimpleInput';
-
-it('should render SimpleInput', () => {
- const onChange = jest.fn();
- const simpleInput = shallow(
- <InputForNumber
- isDefault={false}
- name="foo"
- onChange={onChange}
- setting={mockSetting()}
- value={17}
- />
- ).find(SimpleInput);
- expect(simpleInput.length).toBe(1);
- expect(simpleInput.prop('name')).toBe('foo');
- expect(simpleInput.prop('value')).toBe(17);
- expect(simpleInput.prop('type')).toBe('text');
- expect(simpleInput.prop('onChange')).toBeDefined();
-});
+++ /dev/null
-/*
- * 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();
-});
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { mockSetting } from '../../../../../helpers/mocks/settings';
-import InputForString from '../InputForString';
-import SimpleInput from '../SimpleInput';
-
-it('should render SimpleInput', () => {
- const onChange = jest.fn();
- const simpleInput = shallow(
- <InputForString
- isDefault={false}
- name="foo"
- onChange={onChange}
- setting={mockSetting()}
- value="bar"
- />
- ).find(SimpleInput);
- expect(simpleInput.length).toBe(1);
- expect(simpleInput.prop('name')).toBe('foo');
- expect(simpleInput.prop('value')).toBe('bar');
- expect(simpleInput.prop('type')).toBe('text');
- expect(simpleInput.prop('onChange')).toBeDefined();
-});
+++ /dev/null
-/*
- * 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()}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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",
- ]
- }
-/>
-`;
*/
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';
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 = () => {
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>
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
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
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
| 'onMouseOver'
| 'onMouseLeave'
| 'tabIndex'
+ | 'role'
>;
interface ButtonProps extends AllowedButtonAttributes {
* 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';
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);
});
});
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { NewCodePeriod, NewCodePeriodSettingType } from '../../types/types';
+
+export function mockNewCodePeriod(overrides: Partial<NewCodePeriod> = {}) {
+ return {
+ type: NewCodePeriodSettingType.PREVIOUS_VERSION,
+ ...overrides,
+ };
+}
import {
ExtendedSettingDefinition,
Setting,
+ SettingFieldDefinition,
SettingType,
SettingValue,
SettingWithCategory,
};
}
+export function mockSettingFieldDefinition(
+ overrides: Partial<SettingFieldDefinition> = {}
+): SettingFieldDefinition {
+ return { key: 'name', name: 'Name', options: [], ...overrides };
+}
+
export function mockSetting(overrides: Partial<Setting> = {}): Setting {
return {
key: 'foo',
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,
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;
/*
}
export interface SettingFieldDefinition extends SettingDefinition {
- description: string;
name: string;
}
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;
none=None
no_tags=No tags
not_now=Not now
-off=Off
-on=On
or=Or
open=Open
optional=Optional
valid_input=Valid input
-
#------------------------------------------------------------------------------
#
# GENERIC EXPRESSIONS, sorted alphabetically
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