return (dispatch: Dispatch) => {
const keys = ['sonar.lf.aboutText'];
return getValues({ keys: keys.join() }).then(values => {
- dispatch(receiveValues(values));
+ dispatch(receiveValues(keys, values));
});
};
}
componentDidMount() {
const { hasGovernanceExtension } = this.props;
if (hasGovernanceExtension) {
- this.props.fetchValues('sonar.dbcleaner.auditHousekeeping');
+ this.props.fetchValues(['sonar.dbcleaner.auditHousekeeping']);
}
}
import {
Setting,
SettingCategoryDefinition,
- SettingFieldDefinition
+ SettingFieldDefinition,
+ SettingType
} from '../../../types/settings';
import { buildSettingLink, getDefaultValue, getEmptyValue } from '../utils';
it('should work for property sets', () => {
const setting: SettingCategoryDefinition = {
...settingDefinition,
- type: 'PROPERTY_SET',
+ type: SettingType.PROPERTY_SET,
fields
};
expect(getEmptyValue(setting)).toEqual([{ foo: '', bar: null }]);
it('should work for multi values string', () => {
const setting: SettingCategoryDefinition = {
...settingDefinition,
- type: 'STRING',
+ type: SettingType.STRING,
multiValues: true
};
expect(getEmptyValue(setting)).toEqual(['']);
it('should work for multi values boolean', () => {
const setting: SettingCategoryDefinition = {
...settingDefinition,
- type: 'BOOLEAN',
+ type: SettingType.BOOLEAN,
multiValues: true
};
expect(getEmptyValue(setting)).toEqual([null]);
'should work for boolean field when passing "%s"',
(parentValue?: string, expected?: string) => {
const setting: Setting = {
- definition: { key: 'test', options: [], type: 'BOOLEAN' },
+ hasValue: true,
+ definition: { key: 'test', options: [], type: SettingType.BOOLEAN },
parentValue,
key: 'test'
};
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { find } from 'lodash';
-import * as React from 'react';
-import { Helmet } from 'react-helmet-async';
-import { connect } from 'react-redux';
-import { WithRouterProps } from 'react-router';
-import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
-import { translate } from '../../../helpers/l10n';
-import {
- addSideBarClass,
- addWhitePageClass,
- removeSideBarClass,
- removeWhitePageClass
-} from '../../../helpers/pages';
-import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer';
-import { fetchSettings } from '../store/actions';
-import '../styles.css';
-import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
-import AllCategoriesList from './AllCategoriesList';
-import CategoryDefinitionsList from './CategoryDefinitionsList';
-import CATEGORY_OVERRIDES from './CategoryOverrides';
-import PageHeader from './PageHeader';
-
-interface Props {
- component?: T.Component;
- defaultCategory: string;
- fetchSettings(component?: string): Promise<void>;
-}
-
-interface State {
- loading: boolean;
-}
-
-export class App extends React.PureComponent<Props & WithRouterProps, State> {
- mounted = false;
- state: State = { loading: true };
-
- componentDidMount() {
- this.mounted = true;
- addSideBarClass();
- addWhitePageClass();
- this.fetchSettings();
- }
-
- componentDidUpdate(prevProps: Props) {
- if (prevProps.component !== this.props.component) {
- this.fetchSettings();
- }
- }
-
- componentWillUnmount() {
- this.mounted = false;
- removeSideBarClass();
- removeWhitePageClass();
- }
-
- fetchSettings = () => {
- const { component } = this.props;
- this.props.fetchSettings(component && component.key).then(this.stopLoading, this.stopLoading);
- };
-
- stopLoading = () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- };
-
- render() {
- if (this.state.loading) {
- return null;
- }
-
- const { query } = this.props.location;
- const originalCategory = (query.category as string) || this.props.defaultCategory;
- const overriddenCategory = CATEGORY_OVERRIDES[originalCategory.toLowerCase()];
- const selectedCategory = overriddenCategory || originalCategory;
- const foundAdditionalCategory = find(ADDITIONAL_CATEGORIES, c => c.key === selectedCategory);
- const isProjectSettings = this.props.component;
- const shouldRenderAdditionalCategory =
- foundAdditionalCategory &&
- ((isProjectSettings && foundAdditionalCategory.availableForProject) ||
- (!isProjectSettings && foundAdditionalCategory.availableGlobally));
-
- return (
- <div id="settings-page">
- <Suggestions suggestions="settings" />
- <Helmet defer={false} title={translate('settings.page')} />
- <PageHeader component={this.props.component} />
-
- <div className="layout-page">
- <ScreenPositionHelper className="layout-page-side-outer">
- {({ top }) => (
- <div className="layout-page-side" style={{ top }}>
- <div className="layout-page-side-inner">
- <AllCategoriesList
- component={this.props.component}
- defaultCategory={this.props.defaultCategory}
- selectedCategory={selectedCategory}
- />
- </div>
- </div>
- )}
- </ScreenPositionHelper>
-
- <div className="layout-page-main">
- <div className="layout-page-main-inner">
- <div className="big-padded">
- {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
- foundAdditionalCategory.renderComponent({
- component: this.props.component,
- selectedCategory: originalCategory
- })
- ) : (
- <CategoryDefinitionsList
- category={selectedCategory}
- component={this.props.component}
- />
- )}
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-const mapStateToProps = (state: Store) => ({
- defaultCategory: getSettingsAppDefaultCategory(state)
-});
-
-const mapDispatchToProps = { fetchSettings: fetchSettings as any };
-
-export default connect(mapStateToProps, mapDispatchToProps)(App);
</span>
)}
</div>
-
- <Input
- hasValueChanged={hasValueChanged}
- onCancel={this.handleCancel}
- onChange={this.handleChange}
- onSave={this.handleSave}
- setting={setting}
- value={effectiveValue}
- />
-
- <DefinitionActions
- changedValue={changedValue}
- hasError={hasError}
- hasValueChanged={hasValueChanged}
- isDefault={isDefault}
- onCancel={this.handleCancel}
- onReset={this.handleReset}
- onSave={this.handleSave}
- setting={setting}
- />
+ <form>
+ <Input
+ hasValueChanged={hasValueChanged}
+ onCancel={this.handleCancel}
+ onChange={this.handleChange}
+ onSave={this.handleSave}
+ setting={setting}
+ value={effectiveValue}
+ />
+ <DefinitionActions
+ changedValue={changedValue}
+ hasError={hasError}
+ hasValueChanged={hasValueChanged}
+ isDefault={isDefault}
+ onCancel={this.handleCancel}
+ onReset={this.handleReset}
+ onSave={this.handleSave}
+ setting={setting}
+ />
+ </form>
</div>
</div>
);
import Modal from '../../../components/controls/Modal';
import { translate } from '../../../helpers/l10n';
import { Setting } from '../../../types/settings';
-import { getDefaultValue, getSettingValue, isEmptyValue } from '../utils';
+import { getDefaultValue, isEmptyValue } from '../utils';
type Props = {
changedValue: string;
}
render() {
- const { setting, isDefault, changedValue, hasValueChanged } = this.props;
-
- const hasValueToResetTo = !isEmptyValue(setting.definition, getSettingValue(setting));
+ const { setting, changedValue, isDefault, hasValueChanged } = this.props;
const hasBeenChangedToEmptyValue =
changedValue != null && isEmptyValue(setting.definition, changedValue);
- const showReset =
- hasValueToResetTo && (hasBeenChangedToEmptyValue || (!isDefault && !hasValueChanged));
+ const showReset = hasBeenChangedToEmptyValue || (!isDefault && setting.hasValue);
return (
<>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { find } from 'lodash';
+import * as React from 'react';
+import { Helmet } from 'react-helmet-async';
+import { connect } from 'react-redux';
+import { WithRouterProps } from 'react-router';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
+import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
+import { translate } from '../../../helpers/l10n';
+import {
+ addSideBarClass,
+ addWhitePageClass,
+ removeSideBarClass,
+ removeWhitePageClass
+} from '../../../helpers/pages';
+import { getSettingsAppDefaultCategory, Store } from '../../../store/rootReducer';
+import { fetchSettings } from '../store/actions';
+import '../styles.css';
+import { ADDITIONAL_CATEGORIES } from './AdditionalCategories';
+import AllCategoriesList from './AllCategoriesList';
+import CategoryDefinitionsList from './CategoryDefinitionsList';
+import CATEGORY_OVERRIDES from './CategoryOverrides';
+import PageHeader from './PageHeader';
+
+interface Props {
+ component?: T.Component;
+ defaultCategory: string;
+ fetchSettings(component?: string): Promise<void>;
+}
+
+interface State {
+ loading: boolean;
+}
+
+export class SettingsApp extends React.PureComponent<Props & WithRouterProps, State> {
+ mounted = false;
+ state: State = { loading: true };
+
+ componentDidMount() {
+ this.mounted = true;
+ addSideBarClass();
+ addWhitePageClass();
+ this.fetchSettings();
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.component !== this.props.component) {
+ this.fetchSettings();
+ }
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ removeSideBarClass();
+ removeWhitePageClass();
+ }
+
+ fetchSettings = () => {
+ const { component } = this.props;
+ this.props.fetchSettings(component && component.key).then(this.stopLoading, this.stopLoading);
+ };
+
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
+ }
+ };
+
+ render() {
+ if (this.state.loading) {
+ return null;
+ }
+
+ const { query } = this.props.location;
+ const originalCategory = (query.category as string) || this.props.defaultCategory;
+ const overriddenCategory = CATEGORY_OVERRIDES[originalCategory.toLowerCase()];
+ const selectedCategory = overriddenCategory || originalCategory;
+ const foundAdditionalCategory = find(ADDITIONAL_CATEGORIES, c => c.key === selectedCategory);
+ const isProjectSettings = this.props.component;
+ const shouldRenderAdditionalCategory =
+ foundAdditionalCategory &&
+ ((isProjectSettings && foundAdditionalCategory.availableForProject) ||
+ (!isProjectSettings && foundAdditionalCategory.availableGlobally));
+
+ return (
+ <div id="settings-page">
+ <Suggestions suggestions="settings" />
+ <Helmet defer={false} title={translate('settings.page')} />
+ <PageHeader component={this.props.component} />
+
+ <div className="layout-page">
+ <ScreenPositionHelper className="layout-page-side-outer">
+ {({ top }) => (
+ <div className="layout-page-side" style={{ top }}>
+ <div className="layout-page-side-inner">
+ <AllCategoriesList
+ component={this.props.component}
+ defaultCategory={this.props.defaultCategory}
+ selectedCategory={selectedCategory}
+ />
+ </div>
+ </div>
+ )}
+ </ScreenPositionHelper>
+
+ <div className="layout-page-main">
+ <div className="layout-page-main-inner">
+ <div className="big-padded">
+ {foundAdditionalCategory && shouldRenderAdditionalCategory ? (
+ foundAdditionalCategory.renderComponent({
+ component: this.props.component,
+ selectedCategory: originalCategory
+ })
+ ) : (
+ <CategoryDefinitionsList
+ category={selectedCategory}
+ component={this.props.component}
+ />
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+const mapStateToProps = (state: Store) => ({
+ defaultCategory: getSettingsAppDefaultCategory(state)
+});
+
+const mapDispatchToProps = { fetchSettings: fetchSettings as any };
+
+export default connect(mapStateToProps, mapDispatchToProps)(SettingsApp);
};
fetchValues() {
- const keys = this.props.settings.map(setting => setting.definition.key).join();
+ const keys = this.props.settings.map(setting => setting.definition.key);
return this.props.fetchValues(keys, this.props.component && this.props.component.key);
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
-import {
- addSideBarClass,
- addWhitePageClass,
- removeSideBarClass,
- removeWhitePageClass
-} from '../../../../helpers/pages';
-import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import {
- ALM_INTEGRATION,
- ANALYSIS_SCOPE_CATEGORY,
- LANGUAGES_CATEGORY,
- NEW_CODE_PERIOD_CATEGORY,
- PULL_REQUEST_DECORATION_BINDING_CATEGORY
-} from '../AdditionalCategoryKeys';
-import { App } from '../AppContainer';
-
-jest.mock('../../../../helpers/pages', () => ({
- addSideBarClass: jest.fn(),
- addWhitePageClass: jest.fn(),
- removeSideBarClass: jest.fn(),
- removeWhitePageClass: jest.fn()
-}));
-
-it('should render default view correctly', async () => {
- const wrapper = shallowRender();
-
- expect(addSideBarClass).toBeCalled();
- expect(addWhitePageClass).toBeCalled();
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
- expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot();
-
- wrapper.unmount();
-
- expect(removeSideBarClass).toBeCalled();
- expect(removeWhitePageClass).toBeCalled();
-});
-
-it('should render newCodePeriod correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: NEW_CODE_PERIOD_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render languages correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: LANGUAGES_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render analysis scope correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: ANALYSIS_SCOPE_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render ALM integration correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: ALM_INTEGRATION } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render pull request decoration binding correctly', async () => {
- const wrapper = shallowRender({
- location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY } })
- });
-
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<App['props']> = {}) {
- return shallow(
- <App
- defaultCategory="general"
- fetchSettings={jest.fn().mockResolvedValue({})}
- location={mockLocation()}
- params={{}}
- router={mockRouter()}
- routes={[]}
- {...props}
- />
- );
-}
import { mockSetting } from '../../../../helpers/mocks/settings';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { Definition } from '../Definition';
+import DefinitionActions from '../DefinitionActions';
+import Input from '../inputs/Input';
const setting = mockSetting();
const changeValue = jest.fn();
const checkValue = jest.fn();
const wrapper = shallowRender({ changeValue, checkValue });
- wrapper.find('Input').prop<Function>('onChange')(5);
+ wrapper.find(Input).prop<Function>('onChange')(5);
expect(changeValue).toHaveBeenCalledWith(setting.definition.key, 5);
expect(checkValue).toHaveBeenCalledWith(setting.definition.key);
});
const cancelChange = jest.fn();
const passValidation = jest.fn();
const wrapper = shallowRender({ cancelChange, passValidation });
- wrapper.find('Input').prop<Function>('onCancel')();
+ wrapper.find(Input).prop<Function>('onCancel')();
expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
expect(passValidation).toHaveBeenCalledWith(setting.definition.key);
});
it('should correctly save value change', async () => {
const saveValue = jest.fn().mockResolvedValue({});
const wrapper = shallowRender({ changedValue: 10, saveValue });
- wrapper.find('DefinitionActions').prop<Function>('onSave')();
+ wrapper.find(DefinitionActions).prop('onSave')();
await waitAndUpdate(wrapper);
expect(saveValue).toHaveBeenCalledWith(setting.definition.key, undefined);
expect(wrapper.find('AlertSuccessIcon').exists()).toBe(true);
const cancelChange = jest.fn();
const resetValue = jest.fn().mockResolvedValue({});
const wrapper = shallowRender({ cancelChange, changedValue: 10, resetValue });
- wrapper.find('DefinitionActions').prop<Function>('onReset')();
+ wrapper.find(DefinitionActions).prop('onReset')();
await waitAndUpdate(wrapper);
expect(resetValue).toHaveBeenCalledWith(setting.definition.key, undefined);
expect(cancelChange).toHaveBeenCalledWith(setting.definition.key);
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { SettingCategoryDefinition } from '../../../../types/settings';
+import { SettingCategoryDefinition, SettingType } from '../../../../types/settings';
import DefinitionActions from '../DefinitionActions';
const definition: SettingCategoryDefinition = {
name: 'foobar',
options: [],
subCategory: 'bar',
- type: 'STRING'
+ type: SettingType.STRING
};
const settings = {
key: 'key',
+ hasValue: true,
definition,
value: 'baz'
};
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
+import {
+ addSideBarClass,
+ addWhitePageClass,
+ removeSideBarClass,
+ removeWhitePageClass
+} from '../../../../helpers/pages';
+import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import {
+ ALM_INTEGRATION,
+ ANALYSIS_SCOPE_CATEGORY,
+ LANGUAGES_CATEGORY,
+ NEW_CODE_PERIOD_CATEGORY,
+ PULL_REQUEST_DECORATION_BINDING_CATEGORY
+} from '../AdditionalCategoryKeys';
+import { SettingsApp } from '../SettingsApp';
+
+jest.mock('../../../../helpers/pages', () => ({
+ addSideBarClass: jest.fn(),
+ addWhitePageClass: jest.fn(),
+ removeSideBarClass: jest.fn(),
+ removeWhitePageClass: jest.fn()
+}));
+
+it('should render default view correctly', async () => {
+ const wrapper = shallowRender();
+
+ expect(addSideBarClass).toBeCalled();
+ expect(addWhitePageClass).toBeCalled();
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+ expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot();
+
+ wrapper.unmount();
+
+ expect(removeSideBarClass).toBeCalled();
+ expect(removeWhitePageClass).toBeCalled();
+});
+
+it('should render newCodePeriod correctly', async () => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category: NEW_CODE_PERIOD_CATEGORY } })
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render languages correctly', async () => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category: LANGUAGES_CATEGORY } })
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render analysis scope correctly', async () => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category: ANALYSIS_SCOPE_CATEGORY } })
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render ALM integration correctly', async () => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category: ALM_INTEGRATION } })
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('should render pull request decoration binding correctly', async () => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_BINDING_CATEGORY } })
+ });
+
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<SettingsApp['props']> = {}) {
+ return shallow(
+ <SettingsApp
+ defaultCategory="general"
+ fetchSettings={jest.fn().mockResolvedValue({})}
+ location={mockLocation()}
+ params={{}}
+ router={mockRouter()}
+ routes={[]}
+ {...props}
+ />
+ );
+}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render ALM integration correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <withRouter(Connect(withAppState(AlmIntegration)))
- selectedCategory="almintegration"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render analysis scope correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <AnalysisScope
- selectedCategory="exclusions"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render default view correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <Connect(withRouter(SubCategoryDefinitionsList))
- category="general"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render default view correctly 2`] = `
-<div
- className="layout-page-side-outer"
->
- <div
- className="layout-page-side"
- style={
- Object {
- "top": 0,
- }
- }
- >
- <div
- className="layout-page-side-inner"
- >
- <Connect(CategoriesList)
- defaultCategory="general"
- selectedCategory="general"
- />
- </div>
- </div>
-</div>
-`;
-
-exports[`should render languages correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <withRouter(Connect(Languages))
- selectedCategory="languages"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render newCodePeriod correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <NewCodePeriod />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
-
-exports[`should render pull request decoration binding correctly 1`] = `
-<div
- id="settings-page"
->
- <Suggestions
- suggestions="settings"
- />
- <Helmet
- defer={false}
- encodeSpecialCharacters={true}
- title="settings.page"
- />
- <PageHeader />
- <div
- className="layout-page"
- >
- <ScreenPositionHelper
- className="layout-page-side-outer"
- >
- <Component />
- </ScreenPositionHelper>
- <div
- className="layout-page-main"
- >
- <div
- className="layout-page-main-inner"
- >
- <div
- className="big-padded"
- >
- <Connect(withRouter(SubCategoryDefinitionsList))
- category="pull_request_decoration_binding"
- />
- </div>
- </div>
- </div>
- </div>
-</div>
-`;
<div
className="settings-definition-state"
/>
- <Input
- hasValueChanged={false}
- onCancel={[Function]}
- onChange={[Function]}
- onSave={[Function]}
- setting={
- Object {
- "definition": Object {
- "description": "When Foo then Bar",
+ <form>
+ <Input
+ hasValueChanged={false}
+ onCancel={[Function]}
+ onChange={[Function]}
+ onSave={[Function]}
+ setting={
+ Object {
+ "definition": Object {
+ "description": "When Foo then Bar",
+ "key": "foo",
+ "name": "Foo setting",
+ "options": Array [],
+ "type": "INTEGER",
+ },
+ "hasValue": true,
+ "inherited": true,
"key": "foo",
- "name": "Foo setting",
- "options": Array [],
- "type": "INTEGER",
- },
- "inherited": true,
- "key": "foo",
- "value": "42",
+ "value": "42",
+ }
}
- }
- value="42"
- />
- <DefinitionActions
- changedValue={null}
- hasError={false}
- hasValueChanged={false}
- isDefault={true}
- onCancel={[Function]}
- onReset={[Function]}
- onSave={[Function]}
- setting={
- Object {
- "definition": Object {
- "description": "When Foo then Bar",
+ value="42"
+ />
+ <DefinitionActions
+ changedValue={null}
+ hasError={false}
+ hasValueChanged={false}
+ isDefault={true}
+ onCancel={[Function]}
+ onReset={[Function]}
+ onSave={[Function]}
+ setting={
+ Object {
+ "definition": Object {
+ "description": "When Foo then Bar",
+ "key": "foo",
+ "name": "Foo setting",
+ "options": Array [],
+ "type": "INTEGER",
+ },
+ "hasValue": true,
+ "inherited": true,
"key": "foo",
- "name": "Foo setting",
- "options": Array [],
- "type": "INTEGER",
- },
- "inherited": true,
- "key": "foo",
- "value": "42",
+ "value": "42",
+ }
}
- }
- />
+ />
+ </form>
</div>
</div>
`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render ALM integration correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <withRouter(Connect(withAppState(AlmIntegration)))
+ selectedCategory="almintegration"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render analysis scope correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <AnalysisScope
+ selectedCategory="exclusions"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render default view correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <Connect(withRouter(SubCategoryDefinitionsList))
+ category="general"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render default view correctly 2`] = `
+<div
+ className="layout-page-side-outer"
+>
+ <div
+ className="layout-page-side"
+ style={
+ Object {
+ "top": 0,
+ }
+ }
+ >
+ <div
+ className="layout-page-side-inner"
+ >
+ <Connect(CategoriesList)
+ defaultCategory="general"
+ selectedCategory="general"
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render languages correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <withRouter(Connect(Languages))
+ selectedCategory="languages"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render newCodePeriod correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <NewCodePeriod />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render pull request decoration binding correctly 1`] = `
+<div
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <Helmet
+ defer={false}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="layout-page"
+ >
+ <ScreenPositionHelper
+ className="layout-page-side-outer"
+ >
+ <Component />
+ </ScreenPositionHelper>
+ <div
+ className="layout-page-main"
+ >
+ <div
+ className="layout-page-main-inner"
+ >
+ <div
+ className="big-padded"
+ >
+ <Connect(withRouter(SubCategoryDefinitionsList))
+ category="pull_request_decoration_binding"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+`;
"subCategory": "email",
"type": "INTEGER",
},
+ "hasValue": true,
"inherited": true,
"key": "foo",
"value": "42",
"options": Array [],
"subCategory": "qg",
},
+ "hasValue": true,
"inherited": true,
"key": "foo",
"value": "42",
"options": Array [],
"subCategory": "qg",
},
+ "hasValue": true,
"inherited": true,
"key": "foo",
"value": "42",
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { DefaultInputProps, isCategoryDefinition } from '../../utils';
+import { SettingType } from '../../../../types/settings';
+import {
+ DefaultInputProps,
+ DefaultSpecializedInputProps,
+ getUniqueName,
+ isCategoryDefinition,
+ isDefaultOrInherited,
+ isSecuredDefinition
+} from '../../utils';
+import InputForSecured from './InputForSecured';
import MultiValueInput from './MultiValueInput';
import PrimitiveInput from './PrimitiveInput';
import PropertySetInput from './PropertySetInput';
export default function Input(props: DefaultInputProps) {
- const { definition } = props.setting;
+ const { setting } = props;
+ const { definition } = setting;
+ const name = getUniqueName(definition);
+
+ let Input: React.ComponentType<DefaultSpecializedInputProps> = PrimitiveInput;
if (isCategoryDefinition(definition) && definition.multiValues) {
- return <MultiValueInput {...props} />;
+ Input = MultiValueInput;
+ }
+
+ if (definition.type === SettingType.PROPERTY_SET) {
+ Input = PropertySetInput;
}
- if (definition.type === 'PROPERTY_SET') {
- return <PropertySetInput {...props} />;
+ if (isSecuredDefinition(definition)) {
+ return <InputForSecured input={Input} {...props} />;
}
- return <PrimitiveInput {...props} />;
+ return <Input {...props} name={name} isDefault={isDefaultOrInherited(setting)} />;
}
import SimpleInput from './SimpleInput';
export default function InputForNumber(props: DefaultSpecializedInputProps) {
- return <SimpleInput {...props} className="input-small" type="text" />;
+ return <SimpleInput className="input-small" type="text" {...props} />;
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { colors } from '../../../../app/theme';
-import { Button } from '../../../../components/controls/buttons';
-import LockIcon from '../../../../components/icons/LockIcon';
-import { translate } from '../../../../helpers/l10n';
import { DefaultSpecializedInputProps } from '../../utils';
+import SimpleInput from './SimpleInput';
-interface State {
- changing: boolean;
-}
-
-export default class InputForPassword extends React.PureComponent<
- DefaultSpecializedInputProps,
- State
-> {
- state: State = {
- changing: !this.props.value
- };
-
- componentWillReceiveProps(nextProps: DefaultSpecializedInputProps) {
- /*
- * Reset `changing` if:
- * - the value is reset (valueChanged -> !valueChanged)
- * or
- * - the value changes from outside the input (i.e. store update/reset/cancel)
- */
- if (
- (this.props.hasValueChanged || this.props.value !== nextProps.value) &&
- !nextProps.hasValueChanged
- ) {
- this.setState({ changing: !nextProps.value });
- }
- }
-
- handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.props.onChange(event.target.value);
- };
-
- handleChangeClick = () => {
- this.setState({ changing: true });
- };
-
- renderInput() {
- return (
- <form>
- <input className="hidden" type="password" />
- <input
- autoComplete="off"
- autoFocus={this.state.changing && this.props.value}
- className="js-password-input settings-large-input text-top"
- name={this.props.name}
- onChange={this.handleInputChange}
- type="password"
- value={this.props.value}
- />
- </form>
- );
- }
-
- render() {
- if (this.state.changing) {
- return this.renderInput();
- }
-
- return (
- <>
- <LockIcon className="text-middle big-spacer-right" fill={colors.gray60} />
- <Button className="text-middle" onClick={this.handleChangeClick}>
- {translate('change_verb')}
- </Button>
- </>
- );
- }
+export default function InputForPassword(props: DefaultSpecializedInputProps) {
+ return (
+ <SimpleInput {...props} className="settings-large-input" type="password" autoComplete="off" />
+ );
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 * as React from 'react';
+import { colors } from '../../../../app/theme';
+import { Button } from '../../../../components/controls/buttons';
+import LockIcon from '../../../../components/icons/LockIcon';
+import { translate } from '../../../../helpers/l10n';
+import {
+ DefaultInputProps,
+ DefaultSpecializedInputProps,
+ getUniqueName,
+ isDefaultOrInherited
+} from '../../utils';
+
+interface State {
+ changing: boolean;
+}
+
+interface Props extends DefaultInputProps {
+ input: React.ComponentType<DefaultSpecializedInputProps>;
+}
+
+export default class InputForSecured extends React.PureComponent<Props, State> {
+ state: State = {
+ changing: !this.props.setting.hasValue
+ };
+
+ componentWillReceiveProps(nextProps: Props) {
+ /*
+ * Reset `changing` if:
+ * - the value is reset (valueChanged -> !valueChanged)
+ * or
+ * - the value changes from outside the input (i.e. store update/reset/cancel)
+ */
+ if (
+ (this.props.hasValueChanged || this.props.setting !== nextProps.setting) &&
+ !nextProps.hasValueChanged
+ ) {
+ this.setState({ changing: !nextProps.setting.hasValue });
+ }
+ }
+
+ handleInputChange = (value: string) => {
+ this.props.onChange(value);
+ };
+
+ handleChangeClick = () => {
+ this.setState({ changing: true });
+ };
+
+ renderInput() {
+ const { input: Input, setting, value } = this.props;
+ const name = getUniqueName(setting.definition);
+ return (
+ // The input hidden will prevent browser asking for saving login information
+ <>
+ <input className="hidden" type="password" />
+ <Input
+ autoComplete="off"
+ className="js-setting-input settings-large-input"
+ isDefault={isDefaultOrInherited(setting)}
+ name={name}
+ onChange={this.handleInputChange}
+ setting={setting}
+ type="password"
+ value={value}
+ />
+ </>
+ );
+ }
+
+ render() {
+ if (this.state.changing) {
+ return this.renderInput();
+ }
+
+ return (
+ <>
+ <LockIcon className="text-middle big-spacer-right" fill={colors.gray60} />
+ <Button className="text-middle" onClick={this.handleChangeClick}>
+ {translate('change_verb')}
+ </Button>
+ </>
+ );
+ }
+}
import SimpleInput from './SimpleInput';
export default function InputForString(props: DefaultSpecializedInputProps) {
- return <SimpleInput {...props} className="settings-large-input" type="text" />;
+ return <SimpleInput className="settings-large-input" type="text" {...props} />;
}
*/
import * as React from 'react';
import { DeleteButton } from '../../../../components/controls/buttons';
-import { DefaultInputProps, getEmptyValue } from '../../utils';
+import { DefaultSpecializedInputProps, getEmptyValue } from '../../utils';
import PrimitiveInput from './PrimitiveInput';
-export default class MultiValueInput extends React.PureComponent<DefaultInputProps> {
+export default class MultiValueInput extends React.PureComponent<DefaultSpecializedInputProps> {
ensureValue = () => {
return this.props.value || [];
};
};
renderInput(value: any, index: number, isLast: boolean) {
- const { setting } = this.props;
+ const { setting, isDefault, name } = this.props;
return (
<li className="spacer-bottom" key={index}>
<PrimitiveInput
+ isDefault={isDefault}
+ name={name}
hasValueChanged={this.props.hasValueChanged}
onChange={value => this.handleSingleInputChange(index, value)}
- setting={{
- ...setting,
- definition: { ...setting.definition, multiValues: false },
- values: undefined
- }}
+ setting={setting}
value={value}
/>
*/
import * as React from 'react';
import { SettingType } from '../../../../types/settings';
-import {
- DefaultInputProps,
- DefaultSpecializedInputProps,
- getUniqueName,
- isDefaultOrInherited
-} from '../../utils';
+import { DefaultSpecializedInputProps } from '../../utils';
import InputForBoolean from './InputForBoolean';
import InputForJSON from './InputForJSON';
import InputForNumber from './InputForNumber';
import InputForString from './InputForString';
import InputForText from './InputForText';
-const typeMapping: {
- [type in SettingType]?: React.ComponentType<DefaultSpecializedInputProps>;
-} = {
- STRING: InputForString,
- TEXT: InputForText,
- JSON: InputForJSON,
- PASSWORD: InputForPassword,
- BOOLEAN: InputForBoolean,
- INTEGER: InputForNumber,
- LONG: InputForNumber,
- FLOAT: InputForNumber
-};
-
-interface Props extends DefaultInputProps {
- name?: string;
+function withOptions(options: string[]): React.ComponentType<DefaultSpecializedInputProps> {
+ return function Wrapped(props: DefaultSpecializedInputProps) {
+ return <InputForSingleSelectList options={options} {...props} />;
+ };
}
-export default class PrimitiveInput extends React.PureComponent<Props> {
- render() {
- const { setting, ...other } = this.props;
- const { definition } = setting;
-
- const name = this.props.name || getUniqueName(definition);
+export default function PrimitiveInput(props: DefaultSpecializedInputProps) {
+ const { setting, name, isDefault, ...other } = props;
+ const { definition } = setting;
+ const typeMapping: {
+ [type in SettingType]?: React.ComponentType<DefaultSpecializedInputProps>;
+ } = {
+ STRING: InputForString,
+ TEXT: InputForText,
+ JSON: InputForJSON,
+ PASSWORD: InputForPassword,
+ BOOLEAN: InputForBoolean,
+ INTEGER: InputForNumber,
+ LONG: InputForNumber,
+ FLOAT: InputForNumber,
+ SINGLE_SELECT_LIST: withOptions(definition.options)
+ };
- if (definition.type === 'SINGLE_SELECT_LIST') {
- return (
- <InputForSingleSelectList
- isDefault={isDefaultOrInherited(setting)}
- name={name}
- options={definition.options}
- {...other}
- />
- );
- }
+ const InputComponent = (definition.type && typeMapping[definition.type]) || InputForString;
- const InputComponent = (definition.type && typeMapping[definition.type]) || InputForString;
- return <InputComponent isDefault={isDefaultOrInherited(setting)} name={name} {...other} />;
- }
+ return <InputComponent isDefault={isDefault} name={name} setting={setting} {...other} />;
}
*/
import * as React from 'react';
import { DeleteButton } from '../../../../components/controls/buttons';
-import { DefaultInputProps, getEmptyValue, getUniqueName, isCategoryDefinition } from '../../utils';
+import {
+ DefaultSpecializedInputProps,
+ getEmptyValue,
+ getUniqueName,
+ isCategoryDefinition
+} from '../../utils';
import PrimitiveInput from './PrimitiveInput';
-export default class PropertySetInput extends React.PureComponent<DefaultInputProps> {
+export default class PropertySetInput extends React.PureComponent<DefaultSpecializedInputProps> {
ensureValue() {
return this.props.value || [];
}
};
renderFields(fieldValues: any, index: number, isLast: boolean) {
- const { setting } = this.props;
+ const { setting, isDefault } = this.props;
const { definition } = setting;
return (
<tr key={index}>
{isCategoryDefinition(definition) &&
- definition.fields.map(field => (
- <td key={field.key}>
- <PrimitiveInput
- hasValueChanged={this.props.hasValueChanged}
- name={getUniqueName(definition, field.key)}
- onChange={value => this.handleInputChange(index, field.key, value)}
- setting={{ ...setting, definition: field, value: fieldValues[field.key] }}
- value={fieldValues[field.key]}
- />
- </td>
- ))}
+ definition.fields.map(field => {
+ const newSetting = {
+ ...setting,
+ definition: field,
+ value: fieldValues[field.key]
+ };
+ return (
+ <td key={field.key}>
+ <PrimitiveInput
+ isDefault={isDefault}
+ hasValueChanged={this.props.hasValueChanged}
+ name={getUniqueName(definition, field.key)}
+ onChange={value => this.handleInputChange(index, field.key, value)}
+ setting={newSetting}
+ value={fieldValues[field.key]}
+ />
+ </td>
+ );
+ })}
<td className="thin nowrap text-middle">
{!isLast && (
<DeleteButton
import { DefaultSpecializedInputProps } from '../../utils';
interface Props extends DefaultSpecializedInputProps {
- className?: string;
- type: string;
value: string | number;
}
};
render() {
+ const { autoComplete, autoFocus, className, name, value = '', type } = this.props;
return (
<input
- className={classNames('text-top', this.props.className)}
- name={this.props.name}
+ autoComplete={autoComplete}
+ autoFocus={autoFocus}
+ className={classNames('text-top', className)}
+ name={name}
onChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
- type={this.props.type}
- value={this.props.value || ''}
+ type={type}
+ value={value}
/>
);
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { Setting, SettingCategoryDefinition } from '../../../../../types/settings';
+import { mockDefinition, mockSetting } from '../../../../../helpers/mocks/settings';
+import { Setting, SettingType } from '../../../../../types/settings';
import { DefaultInputProps } from '../../../utils';
import Input from '../Input';
-
-const settingValue = {
- key: 'example'
-};
-
-const settingDefinition: SettingCategoryDefinition = {
- category: 'general',
- fields: [],
- key: 'example',
- options: [],
- subCategory: 'Branches',
- type: 'STRING'
-};
+import InputForSecured from '../InputForSecured';
+import MultiValueInput from '../MultiValueInput';
+import PrimitiveInput from '../PrimitiveInput';
+import PropertySetInput from '../PropertySetInput';
it('should render PrimitiveInput', () => {
- const setting = { ...settingValue, definition: settingDefinition };
const onChange = jest.fn();
- const input = shallowRender({ onChange, setting }).find('PrimitiveInput');
+ 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('setting')).toBe(setting);
expect(input.prop('value')).toBe('foo');
expect(input.prop('onChange')).toBe(onChange);
});
it('should render MultiValueInput', () => {
- const setting = { ...settingValue, definition: { ...settingDefinition, multiValues: true } };
+ const setting = mockSetting({
+ definition: mockDefinition({ multiValues: true })
+ });
const onChange = jest.fn();
const value = ['foo', 'bar'];
- const input = shallowRender({ onChange, setting, value }).find('MultiValueInput');
+ 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);
});
it('should render PropertySetInput', () => {
- const setting: Setting = {
- ...settingValue,
- definition: { ...settingDefinition, type: 'PROPERTY_SET' }
- };
+ 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');
+ 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);
});
function shallowRender(props: Partial<DefaultInputProps> = {}) {
- return shallow(
- <Input
- onChange={jest.fn()}
- setting={{ ...settingValue, definition: settingDefinition }}
- value="foo"
- {...props}
- />
- );
+ return shallow(<Input onChange={jest.fn()} setting={mockSetting()} value="foo" {...props} />);
}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForBoolean from '../InputForBoolean';
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow(
- <InputForBoolean isDefault={false} name="foo" onChange={jest.fn()} value={true} {...props} />
+ <InputForBoolean
+ isDefault={false}
+ name="foo"
+ onChange={jest.fn()}
+ setting={mockSetting()}
+ value={true}
+ {...props}
+ />
);
}
*/
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';
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow<InputForJSON>(
- <InputForJSON isDefault={false} name="foo" onChange={jest.fn()} value="" {...props} />
+ <InputForJSON
+ isDefault={false}
+ name="foo"
+ onChange={jest.fn()}
+ setting={mockSetting()}
+ value=""
+ {...props}
+ />
);
}
*/
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} value={17} />
+ <InputForNumber
+ isDefault={false}
+ name="foo"
+ onChange={onChange}
+ setting={mockSetting()}
+ value={17}
+ />
).find(SimpleInput);
expect(simpleInput.length).toBe(1);
expect(simpleInput.prop('name')).toBe('foo');
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import { change, click, submit } from '../../../../../helpers/testUtils';
-import { DefaultSpecializedInputProps } from '../../../utils';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import InputForPassword from '../InputForPassword';
+import SimpleInput from '../SimpleInput';
-it('should render lock icon, but no form', () => {
+it('should render SimpleInput', () => {
const onChange = jest.fn();
- const input = shallowRender({ onChange });
-
- expect(input.find('LockIcon').length).toBe(1);
- expect(input.find('form').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('form').length).toBe(1);
-});
-
-it('should set value', () => {
- const onChange = jest.fn(() => Promise.resolve());
- const input = shallowRender({ onChange });
-
- click(input.find('Button'));
- change(input.find('.js-password-input'), 'secret');
- submit(input.find('form'));
- expect(onChange).toBeCalledWith('secret');
-});
-
-it('should show form when empty, and enable handle typing', () => {
- const input = shallowRender({ value: '' });
- const onChange = (value: string) => input.setProps({ hasValueChanged: true, value });
- input.setProps({ onChange });
-
- expect(input.find('form').length).toBe(1);
- change(input.find('input.js-password-input'), 'hello');
- expect(input.find('form').length).toBe(1);
- expect(input.find('input.js-password-input').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, value: '' });
-
- expect(input.state('changing')).toBe(true);
-});
-
-function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
- return shallow<InputForPassword>(
+ const simpleInput = shallow(
<InputForPassword
- hasValueChanged={false}
isDefault={false}
name="foo"
- onChange={jest.fn()}
+ onChange={onChange}
+ setting={mockSetting()}
value="bar"
- {...props}
/>
- );
-}
+ ).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-2021 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).toBeCalledWith('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}
+ />
+ );
+}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { DefaultSpecializedInputProps } from '../../../utils';
import InputForSingleSelectList from '../InputForSingleSelectList';
name="foo"
onChange={jest.fn()}
options={['foo', 'bar', 'baz']}
+ setting={mockSetting()}
value="bar"
{...props}
/>
*/
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} value="bar" />
+ <InputForString
+ isDefault={false}
+ name="foo"
+ onChange={onChange}
+ setting={mockSetting()}
+ value="bar"
+ />
).find(SimpleInput);
expect(simpleInput.length).toBe(1);
expect(simpleInput.prop('name')).toBe('foo');
*/
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';
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow(
- <InputForText isDefault={false} name="foo" onChange={jest.fn()} value="bar" {...props} />
+ <InputForText
+ isDefault={false}
+ name="foo"
+ onChange={jest.fn()}
+ value="bar"
+ {...props}
+ setting={mockSetting()}
+ />
);
}
import { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import { click } from '../../../../../helpers/testUtils';
-import { SettingCategoryDefinition } from '../../../../../types/settings';
-import { DefaultInputProps } from '../../../utils';
+import { SettingCategoryDefinition, SettingType } from '../../../../../types/settings';
+import { DefaultSpecializedInputProps } from '../../../utils';
import MultiValueInput from '../MultiValueInput';
import PrimitiveInput from '../PrimitiveInput';
const settingValue = {
- key: 'example'
+ key: 'example',
+ hasValue: true
};
const settingDefinition: SettingCategoryDefinition = {
multiValues: true,
options: [],
subCategory: 'Branches',
- type: 'STRING'
+ type: SettingType.STRING
};
const assertValues = (inputs: ShallowWrapper<any>, values: string[]) => {
expect(onChange).toBeCalledWith(['foo', 'bar']);
});
-function shallowRender(props: Partial<DefaultInputProps> = {}) {
+function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) {
return shallow(
<MultiValueInput
+ isDefault={true}
+ name="bar"
onChange={jest.fn()}
setting={{ ...settingValue, definition: settingDefinition }}
value={['foo']}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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}
+ />
+ );
+}
*/
import { shallow } from 'enzyme';
import * as React from 'react';
+import { mockSetting } from '../../../../../helpers/mocks/settings';
import { change } from '../../../../../helpers/testUtils';
import SimpleInput from '../SimpleInput';
name="foo"
onChange={onChange}
type="text"
+ setting={mockSetting()}
value="bar"
/>
).find('input');
name="foo"
onChange={onChange}
type="text"
+ setting={mockSetting()}
value="bar"
/>
).find('input');
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly for BOOLEAN 1`] = `
+<InputForBoolean
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "BOOLEAN",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for FLOAT 1`] = `
+<InputForNumber
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "FLOAT",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for INTEGER 1`] = `
+<InputForNumber
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "INTEGER",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for JSON 1`] = `
+<InputForJSON
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "JSON",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for LICENSE 1`] = `
+<InputForString
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "LICENSE",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for LONG 1`] = `
+<InputForNumber
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "LONG",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for PASSWORD 1`] = `
+<InputForPassword
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "PASSWORD",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for PROPERTY_SET 1`] = `
+<InputForString
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "PROPERTY_SET",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for SINGLE_SELECT_LIST 1`] = `
+<Wrapped
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "SINGLE_SELECT_LIST",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for STRING 1`] = `
+<InputForString
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "STRING",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
+
+exports[`should render correctly for TEXT 1`] = `
+<InputForText
+ isDefault={true}
+ name="name"
+ onChange={[MockFunction]}
+ setting={
+ Object {
+ "definition": Object {
+ "category": "foo category",
+ "fields": Array [],
+ "key": "foo",
+ "options": Array [],
+ "subCategory": "foo subCat",
+ "type": "TEXT",
+ },
+ "hasValue": true,
+ "inherited": true,
+ "key": "foo",
+ "value": "42",
+ }
+ }
+ value={
+ Array [
+ "foo",
+ ]
+ }
+/>
+`;
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
+import Toggle from '../../../../components/controls/Toggle';
import { Alert } from '../../../../components/ui/Alert';
import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants';
AlmSettingsInstance,
ProjectAlmBindingResponse
} from '../../../../types/alm-settings';
-import InputForBoolean from '../inputs/InputForBoolean';
export interface AlmSpecificFormProps {
alm: AlmKeys;
return renderFieldWrapper(
renderLabel({ ...props, optional: true }),
<div className="display-flex-center big-spacer-top">
- <InputForBoolean
- isDefault={true}
- name={id}
- onChange={v => onFieldChange(propKey, v)}
- value={value}
- />
+ <div className="display-inline-block text-top">
+ <Toggle name={id} onChange={v => onFieldChange(propKey, v)} value={value} />
+ {value == null && <span className="spacer-left note">{translate('settings.not_set')}</span>}
+ </div>
{inputExtra}
</div>,
renderHelp(props)
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="github.summary_comment_setting"
- onChange={[Function]}
- value={true}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="github.summary_comment_setting"
+ onChange={[Function]}
+ value={true}
+ />
+ </div>
</div>
</div>
</div>
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="github.summary_comment_setting"
- onChange={[Function]}
- value={true}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="github.summary_comment_setting"
+ onChange={[Function]}
+ value={true}
+ />
+ </div>
</div>
</div>
</div>
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="github.summary_comment_setting"
- onChange={[Function]}
- value={true}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="github.summary_comment_setting"
+ onChange={[Function]}
+ value={true}
+ />
+ </div>
</div>
</div>
</div>
<div
className="display-flex-center big-spacer-top"
>
- <InputForBoolean
- isDefault={true}
- name="monorepo"
- onChange={[Function]}
- value={false}
- />
+ <div
+ className="display-inline-block text-top"
+ >
+ <Toggle
+ name="monorepo"
+ onChange={[Function]}
+ value={false}
+ />
+ </div>
</div>
</div>
</div>
const routes = [
{
- indexRoute: { component: lazyLoadComponent(() => import('./components/AppContainer')) }
+ indexRoute: { component: lazyLoadComponent(() => import('./components/SettingsApp')) }
},
{
path: 'encryption',
getSettingsAppChangedValue,
getSettingsAppDefinition
} from '../../../../store/rootReducer';
-import { checkValue, fetchSettings } from '../actions';
+import { checkValue, fetchSettings, fetchValues } from '../actions';
import { receiveDefinitions } from '../definitions';
-jest.mock('../../../../api/settings', () => ({
- getDefinitions: jest.fn().mockResolvedValue([
- {
- key: 'SETTINGS_1_KEY',
- type: 'SETTINGS_1_TYPE'
- },
- {
- key: 'SETTINGS_2_KEY',
- type: 'LICENSE'
- }
- ])
-}));
+jest.mock('../../../../api/settings', () => {
+ const { mockSettingValue } = jest.requireActual('../../../../helpers/mocks/settings');
+ return {
+ getValues: jest.fn().mockResolvedValue([mockSettingValue()]),
+ getDefinitions: jest.fn().mockResolvedValue([
+ {
+ key: 'SETTINGS_1_KEY',
+ type: 'SETTINGS_1_TYPE'
+ },
+ {
+ key: 'SETTINGS_2_KEY',
+ type: 'LICENSE'
+ }
+ ])
+ };
+});
jest.mock('../definitions', () => ({
receiveDefinitions: jest.fn()
]);
});
+it('should fetchValue correclty', async () => {
+ const dispatch = jest.fn();
+ await fetchValues(['test'], 'foo')(dispatch);
+ expect(dispatch).toHaveBeenCalledWith({
+ component: 'foo',
+ settings: [{ key: 'test' }],
+ type: 'RECEIVE_VALUES',
+ updateKeys: ['test']
+ });
+ expect(dispatch).toHaveBeenCalledWith({ type: 'CLOSE_ALL_GLOBAL_MESSAGES' });
+});
+
describe('checkValue', () => {
const dispatch = jest.fn();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { getSettingsForCategory } from '../rootReducer';
+
+it('Should correclty assert if value is set', () => {
+ const settings = getSettingsForCategory(
+ {
+ definitions: {
+ foo: { category: 'cat', key: 'foo', fields: [], options: [], subCategory: 'test' },
+ bar: { category: 'cat', key: 'bar', fields: [], options: [], subCategory: 'test' }
+ },
+ globalMessages: [],
+ settingsPage: {
+ changedValues: {},
+ loading: {},
+ validationMessages: {}
+ },
+ values: { components: {}, global: { foo: { key: 'foo' } } }
+ },
+ 'cat'
+ );
+ expect(settings[0].hasValue).toBe(true);
+ expect(settings[1].hasValue).toBe(false);
+});
};
}
-export function fetchValues(keys: string, component?: string) {
+export function fetchValues(keys: string[], component?: string) {
return (dispatch: Dispatch) =>
- getValues({ keys, component }).then(settings => {
- dispatch(receiveValues(settings, component));
+ getValues({ keys: keys.join(), component }).then(settings => {
+ dispatch(receiveValues(keys, settings, component));
dispatch(closeAllGlobalMessages());
});
}
return setSettingValue(definition, value, component)
.then(() => getValues({ keys: key, component }))
.then(values => {
- dispatch(receiveValues(values, component));
+ dispatch(receiveValues([key], values, component));
dispatch(cancelChange(key));
dispatch(passValidation(key));
dispatch(stopLoading(key));
.then(() => getValues({ keys: key, component }))
.then(values => {
if (values.length > 0) {
- dispatch(receiveValues(values, component));
+ dispatch(receiveValues([key], values, component));
} else {
- dispatch(receiveValues([{ key }], component));
+ dispatch(receiveValues([key], [], component));
}
dispatch(passValidation(key));
dispatch(stopLoading(key));
}
export function getSettingsForCategory(state: State, category: string, component?: string) {
- return fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => ({
- key: definition.key,
- ...getValue(state, definition.key, component),
- definition
- }));
+ return fromDefinitions.getDefinitionsForCategory(state.definitions, category).map(definition => {
+ const value = getValue(state, definition.key, component);
+ const hasValue = value !== undefined && value.inherited !== true;
+ return {
+ key: definition.key,
+ hasValue,
+ ...value,
+ definition
+ };
+ });
}
export function getChangedValue(state: State, key: string) {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { keyBy } from 'lodash';
+import { keyBy, omit } from 'lodash';
import { combineReducers } from 'redux';
import { Action as AppStateAction, Actions as AppStateActions } from '../../../store/appState';
import { ActionType } from '../../../store/utils/actions';
}
export function receiveValues(
+ updateKeys: string[],
settings: Array<{ key: string; value?: string }>,
component?: string
) {
- return { type: Actions.receiveValues, settings, component };
+ return { type: Actions.receiveValues, updateKeys, settings, component };
}
function components(state: State['components'] = {}, action: Action) {
}
if (action.type === Actions.receiveValues) {
const settingsByKey = keyBy(action.settings, 'key');
- return { ...state, [key]: { ...(state[key] || {}), ...settingsByKey } };
+ return { ...state, [key]: { ...omit(state[key] || {}, action.updateKeys), ...settingsByKey } };
}
return state;
}
return state;
}
const settingsByKey = keyBy(action.settings, 'key');
- return { ...state, ...settingsByKey };
+ return { ...omit(state, action.updateKeys), ...settingsByKey };
}
+
if (action.type === AppStateActions.SetAppState) {
const settingsByKey: SettingsState = {};
Object.keys(action.appState.settings).forEach(
export const DEFAULT_CATEGORY = 'general';
-export type DefaultSpecializedInputProps = T.Omit<DefaultInputProps, 'setting'> & {
+export type DefaultSpecializedInputProps = DefaultInputProps & {
+ className?: string;
+ autoComplete?: string;
isDefault: boolean;
name: string;
+ type?: string;
};
export interface DefaultInputProps {
+ autoFocus?: boolean;
hasValueChanged?: boolean;
onCancel?: () => void;
onChange: (value: any) => void;
}
}
+export function isSecuredDefinition(item: SettingDefinition): boolean {
+ return item.key.endsWith('.secured');
+}
+
export function isCategoryDefinition(item: SettingDefinition): item is SettingCategoryDefinition {
return Boolean((item as any).fields);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { Setting, SettingCategoryDefinition, SettingWithCategory } from '../../types/settings';
+import {
+ Setting,
+ SettingCategoryDefinition,
+ SettingType,
+ SettingValue,
+ SettingWithCategory
+} from '../../types/settings';
export function mockDefinition(
overrides: Partial<SettingCategoryDefinition> = {}
return {
key: 'foo',
value: '42',
+ hasValue: true,
inherited: true,
definition: {
key: 'foo',
name: 'Foo setting',
description: 'When Foo then Bar',
- type: 'INTEGER',
+ type: SettingType.INTEGER,
options: []
},
...overrides
};
}
+export function mockSettingValue(overrides: Partial<SettingValue> = {}) {
+ return {
+ key: 'test',
+ ...overrides
+ };
+}
+
export function mockSettingWithCategory(
overrides: Partial<SettingWithCategory> = {}
): SettingWithCategory {
return {
key: 'foo',
value: '42',
+ hasValue: true,
inherited: true,
definition: {
key: 'foo',
name: 'Foo setting',
description: 'When Foo then Bar',
- type: 'INTEGER',
+ type: SettingType.INTEGER,
options: [],
category: 'general',
fields: [],
ProjectReportFrequency = 'sonar.governance.report.project.branch.frequency'
}
-export type Setting = SettingValue & { definition: SettingDefinition };
+export type Setting = SettingValue & { definition: SettingDefinition; hasValue: boolean };
export type SettingWithCategory = Setting & { definition: SettingCategoryDefinition };
-export type SettingType =
- | 'STRING'
- | 'TEXT'
- | 'JSON'
- | 'PASSWORD'
- | 'BOOLEAN'
- | 'FLOAT'
- | 'INTEGER'
- | 'LICENSE'
- | 'LONG'
- | 'SINGLE_SELECT_LIST'
- | 'PROPERTY_SET';
-
+export enum SettingType {
+ STRING = 'STRING',
+ TEXT = 'TEXT',
+ JSON = 'JSON',
+ PASSWORD = 'PASSWORD',
+ BOOLEAN = 'BOOLEAN',
+ FLOAT = 'FLOAT',
+ INTEGER = 'INTEGER',
+ LICENSE = 'LICENSE',
+ LONG = 'LONG',
+ SINGLE_SELECT_LIST = 'SINGLE_SELECT_LIST',
+ PROPERTY_SET = 'PROPERTY_SET'
+}
export interface SettingDefinition {
description?: string;
key: string;