import PullRequestDecorationBinding from './pullRequestDecorationBinding/PRDecorationBinding';
export interface AdditionalCategoryComponentProps {
- parentComponent: T.Component | undefined;
+ component: T.Component | undefined;
selectedCategory: string;
}
export interface AdditionalCategory {
- key: string;
- name: string;
- renderComponent: (props: AdditionalCategoryComponentProps) => React.ReactNode;
availableGlobally: boolean;
availableForProject: boolean;
displayTab: boolean;
+ key: string;
+ name: string;
+ renderComponent: (props: AdditionalCategoryComponentProps) => React.ReactNode;
requiresBranchesEnabled?: boolean;
}
}
function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) {
- const { parentComponent } = props;
- return parentComponent && <PullRequestDecorationBinding component={parentComponent} />;
+ return props.component && <PullRequestDecorationBinding component={props.component} />;
}
import CategoryDefinitionsList from './CategoryDefinitionsList';
export function AnalysisScope(props: AdditionalCategoryComponentProps) {
- const { parentComponent, selectedCategory } = props;
+ const { component, selectedCategory } = props;
return (
<>
</table>
<div className="settings-sub-category">
- <CategoryDefinitionsList category={selectedCategory} component={parentComponent} />
+ <CategoryDefinitionsList category={selectedCategory} component={component} />
</div>
</>
);
<div className="side-tabs-main">
{foundAdditionalCategory && shouldRenderAdditionalCategory ? (
foundAdditionalCategory.renderComponent({
- parentComponent: this.props.component,
+ component: this.props.component,
selectedCategory: originalCategory
})
) : (
import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer';
import { getCategoryName } from '../utils';
+import { AdditionalCategoryComponentProps } from './AdditionalCategories';
import { LANGUAGES_CATEGORY } from './AdditionalCategoryKeys';
import CategoryDefinitionsList from './CategoryDefinitionsList';
import { CATEGORY_OVERRIDES } from './CategoryOverrides';
-export interface LanguagesProps {
+export interface LanguagesProps extends AdditionalCategoryComponentProps {
categories: string[];
- component?: T.Component;
location: Location;
- selectedCategory: string;
router: Router;
}
-interface LanguagesState {
- availableLanguages: SelectOption[];
- selectedLanguage: string | undefined;
-}
-
interface SelectOption {
label: string;
originalValue: string;
value: string;
}
-export class Languages extends React.PureComponent<LanguagesProps, LanguagesState> {
- constructor(props: LanguagesProps) {
- super(props);
-
- this.state = {
- availableLanguages: [],
- selectedLanguage: undefined
- };
- }
-
- componentDidMount() {
- const { selectedCategory, categories } = this.props;
- const lowerCasedLanguagesCategory = LANGUAGES_CATEGORY.toLowerCase();
- const lowerCasedSelectedCategory = selectedCategory.toLowerCase();
-
- const availableLanguages = categories
- .filter(c => CATEGORY_OVERRIDES[c.toLowerCase()] === lowerCasedLanguagesCategory)
- .map(c => ({
- label: getCategoryName(c),
- value: c.toLowerCase(),
- originalValue: c
- }));
-
- let selectedLanguage = undefined;
-
- if (
- lowerCasedSelectedCategory !== lowerCasedLanguagesCategory &&
- availableLanguages.find(c => c.value === lowerCasedSelectedCategory)
- ) {
- selectedLanguage = lowerCasedSelectedCategory;
- }
-
- this.setState({
- availableLanguages,
- selectedLanguage
- });
- }
-
- handleOnChange = (newOption: SelectOption) => {
- this.setState({ selectedLanguage: newOption.value });
-
- const { location, router } = this.props;
+export function Languages(props: LanguagesProps) {
+ const { categories, component, location, router, selectedCategory } = props;
+ const { availableLanguages, selectedLanguage } = getLanguages(categories, selectedCategory);
+ const handleOnChange = (newOption: SelectOption) => {
router.push({
...location,
query: { ...location.query, category: newOption.originalValue }
});
};
- render() {
- const { component } = this.props;
- const { availableLanguages, selectedLanguage } = this.state;
-
- return (
- <>
- <h2 className="settings-sub-category-name">{translate('property.category.languages')}</h2>
- <div data-test="language-select">
- <Select
- className="input-large"
- onChange={this.handleOnChange}
- options={availableLanguages}
- placeholder={translate('settings.languages.select_a_language_placeholder')}
- value={selectedLanguage}
- />
+ return (
+ <>
+ <h2 className="settings-sub-category-name">{translate('property.category.languages')}</h2>
+ <div data-test="language-select">
+ <Select
+ className="input-large"
+ onChange={handleOnChange}
+ options={availableLanguages}
+ placeholder={translate('settings.languages.select_a_language_placeholder')}
+ value={selectedLanguage}
+ />
+ </div>
+ {selectedLanguage && (
+ <div className="settings-sub-category">
+ <CategoryDefinitionsList category={selectedLanguage} component={component} />
</div>
- {selectedLanguage && (
- <div className="settings-sub-category">
- <CategoryDefinitionsList category={selectedLanguage} component={component} />
- </div>
- )}
- </>
- );
+ )}
+ </>
+ );
+}
+
+function getLanguages(categories: string[], selectedCategory: string) {
+ const lowerCasedLanguagesCategory = LANGUAGES_CATEGORY.toLowerCase();
+ const lowerCasedSelectedCategory = selectedCategory.toLowerCase();
+
+ const availableLanguages = categories
+ .filter(c => CATEGORY_OVERRIDES[c.toLowerCase()] === lowerCasedLanguagesCategory)
+ .map(c => ({
+ label: getCategoryName(c),
+ value: c.toLowerCase(),
+ originalValue: c
+ }));
+
+ let selectedLanguage = undefined;
+
+ if (
+ lowerCasedSelectedCategory !== lowerCasedLanguagesCategory &&
+ availableLanguages.find(c => c.value === lowerCasedSelectedCategory)
+ ) {
+ selectedLanguage = lowerCasedSelectedCategory;
}
+
+ return {
+ availableLanguages,
+ selectedLanguage
+ };
}
export default withRouter(
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 { mockComponent } from '../../../../helpers/testMocks';
+import { ADDITIONAL_CATEGORIES } from '../AdditionalCategories';
+import { PULL_REQUEST_DECORATION_BINDING_CATEGORY } from '../AdditionalCategoryKeys';
+
+it('should render additional categories component correctly', () => {
+ ADDITIONAL_CATEGORIES.forEach(cat => {
+ expect(
+ cat.renderComponent({
+ component: mockComponent(),
+ selectedCategory: 'TEST'
+ })
+ ).toMatchSnapshot();
+ });
+});
+
+it('should not render pull request decoration binding component when the component is not defined', () => {
+ const category = find(
+ ADDITIONAL_CATEGORIES,
+ c => c.key === PULL_REQUEST_DECORATION_BINDING_CATEGORY
+ );
+
+ if (!category) {
+ fail('category should be defined');
+ } else {
+ expect(
+ category.renderComponent({ component: undefined, selectedCategory: '' })
+ ).toBeUndefined();
+ }
+});
});
function shallowRender() {
- return shallow(<AnalysisScope parentComponent={mockComponent()} selectedCategory="TEST" />);
+ return shallow(<AnalysisScope component={mockComponent()} selectedCategory="TEST" />);
}
import {
ANALYSIS_SCOPE_CATEGORY,
LANGUAGES_CATEGORY,
- NEW_CODE_PERIOD_CATEGORY
+ NEW_CODE_PERIOD_CATEGORY,
+ PULL_REQUEST_DECORATION_BINDING_CATEGORY,
+ PULL_REQUEST_DECORATION_CATEGORY
} from '../AdditionalCategoryKeys';
import { App } from '../AppContainer';
expect(wrapper).toMatchSnapshot();
});
+it('should render pull request decoration correctly', async () => {
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { category: PULL_REQUEST_DECORATION_CATEGORY } })
+ });
+
+ 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
* 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 { mockLocation, mockRouter } from '../../../../helpers/testMocks';
+import Select from 'sonar-ui-common/components/controls/Select';
+import { mockComponent, mockLocation, mockRouter } from '../../../../helpers/testMocks';
+import CategoryDefinitionsList from '../CategoryDefinitionsList';
import { Languages, LanguagesProps } from '../Languages';
it('should render correctly', () => {
const push = jest.fn();
const router = mockRouter({ push });
const wrapper = shallowRender({ router });
- expect(wrapper.state().selectedLanguage).toBe('java');
+ expect(wrapper.find(CategoryDefinitionsList).props().category).toBe('java');
+
+ const { onChange } = wrapper.find(Select).props();
+
+ if (!onChange) {
+ fail('onChange should be defined');
+ } else {
+ onChange({ label: '', originalValue: 'CoBoL', value: 'cobol' });
+ expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } }));
+ }
+});
- wrapper.instance().handleOnChange({ label: '', originalValue: 'CoBoL', value: 'cobol' });
- expect(wrapper.state().selectedLanguage).toBe('cobol');
- expect(push).toHaveBeenCalledWith(expect.objectContaining({ query: { category: 'CoBoL' } }));
+it('should correctly show the subcategory for a component', () => {
+ const component = mockComponent();
+ const wrapper = shallowRender({ component });
+ expect(wrapper.find(CategoryDefinitionsList).props().component).toBe(component);
});
function shallowRender(props: Partial<LanguagesProps> = {}) {
- return shallow<Languages>(
+ return shallow(
<Languages
categories={['Java', 'JavaScript', 'COBOL']}
+ component={undefined}
location={mockLocation()}
router={mockRouter()}
selectedCategory="java"
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render additional categories component correctly 1`] = `
+<withRouter(Connect(Languages))
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ selectedCategory="TEST"
+/>
+`;
+
+exports[`should render additional categories component correctly 2`] = `<NewCodePeriod />`;
+
+exports[`should render additional categories component correctly 3`] = `
+<AnalysisScope
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+ selectedCategory="TEST"
+/>
+`;
+
+exports[`should render additional categories component correctly 4`] = `<PullRequestDecoration />`;
+
+exports[`should render additional categories component correctly 5`] = `
+<PRDecorationBinding
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "organization": "foo",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
+ "name": "Sonar way",
+ },
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
+ }
+/>
+`;
</div>
</div>
`;
+
+exports[`should render pull request decoration binding correctly 1`] = `
+<div
+ className="page page-limited"
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="side-tabs-layout settings-layout"
+ >
+ <div
+ className="side-tabs-side"
+ >
+ <Connect(CategoriesList)
+ defaultCategory="general"
+ selectedCategory="pull_request_decoration_binding"
+ />
+ </div>
+ <div
+ className="side-tabs-main"
+ >
+ <Connect(SubCategoryDefinitionsList)
+ category="pull_request_decoration_binding"
+ />
+ </div>
+ </div>
+</div>
+`;
+
+exports[`should render pull request decoration correctly 1`] = `
+<div
+ className="page page-limited"
+ id="settings-page"
+>
+ <Suggestions
+ suggestions="settings"
+ />
+ <HelmetWrapper
+ defer={true}
+ encodeSpecialCharacters={true}
+ title="settings.page"
+ />
+ <PageHeader />
+ <div
+ className="side-tabs-layout settings-layout"
+ >
+ <div
+ className="side-tabs-side"
+ >
+ <Connect(CategoriesList)
+ defaultCategory="general"
+ selectedCategory="pull_request_decoration"
+ />
+ </div>
+ <div
+ className="side-tabs-main"
+ >
+ <PullRequestDecoration />
+ </div>
+ </div>
+</div>
+`;