From 8a0d7256ae199f2305eb3a750258ba38976f4bd2 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 20 Nov 2019 09:06:17 +0100 Subject: [PATCH] SONAR-12500 Bug fix : can't update language's setting at the project level --- .../components/AdditionalCategories.tsx | 11 +- .../settings/components/AnalysisScope.tsx | 4 +- .../apps/settings/components/AppContainer.tsx | 2 +- .../js/apps/settings/components/Languages.tsx | 122 +++++++----------- .../__tests__/AdditionalCategories-test.tsx | 50 +++++++ .../__tests__/AnalysisScope-test.tsx | 2 +- .../__tests__/AppContainer-test.tsx | 22 +++- .../components/__tests__/Languages-test.tsx | 27 +++- .../AdditionalCategories-test.tsx.snap | 91 +++++++++++++ .../__snapshots__/AppContainer-test.tsx.snap | 70 ++++++++++ 10 files changed, 312 insertions(+), 89 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx index 58adf6ca8b3..072620bac6f 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AdditionalCategories.tsx @@ -34,17 +34,17 @@ import PullRequestDecoration from './pullRequestDecoration/PullRequestDecoration 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; } @@ -110,6 +110,5 @@ function getPullRequestDecorationComponent() { } function getPullRequestDecorationBindingComponent(props: AdditionalCategoryComponentProps) { - const { parentComponent } = props; - return parentComponent && ; + return props.component && ; } diff --git a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx index 4bdf1167b2f..2097da92bc4 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AnalysisScope.tsx @@ -25,7 +25,7 @@ import { AdditionalCategoryComponentProps } from './AdditionalCategories'; import CategoryDefinitionsList from './CategoryDefinitionsList'; export function AnalysisScope(props: AdditionalCategoryComponentProps) { - const { parentComponent, selectedCategory } = props; + const { component, selectedCategory } = props; return ( <> @@ -56,7 +56,7 @@ export function AnalysisScope(props: AdditionalCategoryComponentProps) {
- +
); diff --git a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx index 86a8dd1d113..df745050276 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx @@ -109,7 +109,7 @@ export class App extends React.PureComponent {
{foundAdditionalCategory && shouldRenderAdditionalCategory ? ( foundAdditionalCategory.renderComponent({ - parentComponent: this.props.component, + component: this.props.component, selectedCategory: originalCategory }) ) : ( diff --git a/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx b/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx index 35a88f9280a..7ec8a55a8cb 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/Languages.tsx @@ -25,102 +25,80 @@ import { translate } from 'sonar-ui-common/helpers/l10n'; 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 { - 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 ( - <> -

{translate('property.category.languages')}

-
- +
+ {selectedLanguage && ( +
+
- {selectedLanguage && ( -
- -
- )} - - ); + )} + + ); +} + +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( diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx new file mode 100644 index 00000000000..77afccd8e84 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AdditionalCategories-test.tsx @@ -0,0 +1,50 @@ +/* + * 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(); + } +}); diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx index 3711f71960b..df679218c1c 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AnalysisScope-test.tsx @@ -28,5 +28,5 @@ it('should render correctly', () => { }); function shallowRender() { - return shallow(); + return shallow(); } diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx index b7c605015b9..a0f3990d0f6 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx @@ -24,7 +24,9 @@ import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; 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'; @@ -62,6 +64,24 @@ it('should render analysis scope correctly', async () => { 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 = {}) { return shallow( { @@ -36,17 +39,29 @@ it('should correctly handle a change of the selected language', () => { 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 = {}) { - return shallow( + return shallow( +`; + +exports[`should render additional categories component correctly 2`] = ``; + +exports[`should render additional categories component correctly 3`] = ` + +`; + +exports[`should render additional categories component correctly 4`] = ``; + +exports[`should render additional categories component correctly 5`] = ` + +`; diff --git a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap index df09da04ef2..d3f5cdc9a8b 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap @@ -141,3 +141,73 @@ exports[`should render newCodePeriod correctly 1`] = `
`; + +exports[`should render pull request decoration binding correctly 1`] = ` +
+ + + +
+
+ +
+
+ +
+
+
+`; + +exports[`should render pull request decoration correctly 1`] = ` +
+ + + +
+
+ +
+
+ +
+
+
+`; -- 2.39.5