diff options
author | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-08-29 13:05:49 +0200 |
---|---|---|
committer | Stas Vilchik <stas.vilchik@sonarsource.com> | 2017-09-13 13:53:58 +0200 |
commit | 361d9c3e6c2f2dbf10b1412789bed2095f25edcc (patch) | |
tree | 532480cb28ec553e89b9fe9d53dd001f8a68dc58 | |
parent | 77bd653967bee376b0e3ff3be0e6f7a5be32699e (diff) | |
download | sonarqube-361d9c3e6c2f2dbf10b1412789bed2095f25edcc.tar.gz sonarqube-361d9c3e6c2f2dbf10b1412789bed2095f25edcc.zip |
rewrite projects app
201 files changed, 5591 insertions, 2197 deletions
diff --git a/server/sonar-web/src/main/js/app/components/search/SearchResult.js b/server/sonar-web/src/main/js/app/components/search/SearchResult.js index 7b91a192bc9..0070873b0a9 100644 --- a/server/sonar-web/src/main/js/app/components/search/SearchResult.js +++ b/server/sonar-web/src/main/js/app/components/search/SearchResult.js @@ -21,7 +21,7 @@ import React from 'react'; import { Link } from 'react-router'; /*:: import type { Component } from './utils'; */ -import FavoriteIcon from '../../../components/common/FavoriteIcon'; +import FavoriteIcon from '../../../components/icons-components/FavoriteIcon'; import QualifierIcon from '../../../components/shared/QualifierIcon'; import ClockIcon from '../../../components/common/ClockIcon'; import Tooltip from '../../../components/controls/Tooltip'; diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.js.snap index 3372a582f53..91f9b47f6f5 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.js.snap @@ -21,7 +21,6 @@ exports[`should render correctly 1`] = ` </span> <Rating className="spacer-left" - muted={false} small={true} value="5.0" /> diff --git a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts new file mode 100644 index 00000000000..d48601e76ba --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 utils from '../utils'; + +describe('localizeSorting', () => { + it('localizes default sorting', () => { + expect(utils.localizeSorting()).toBe('projects.sort.name'); + }); + + it('localizes custom sorting', () => { + expect(utils.localizeSorting('size')).toBe('projects.sort.size'); + }); +}); + +describe('parseSorting', () => { + it('parses ascending', () => { + expect(utils.parseSorting('size')).toEqual({ sortDesc: false, sortValue: 'size' }); + }); + + it('parses descending', () => { + expect(utils.parseSorting('-size')).toEqual({ sortDesc: true, sortValue: 'size' }); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index b87bf26551d..bca7874cba0 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; +import * as PropTypes from 'prop-types'; import Helmet from 'react-helmet'; import PageHeaderContainer from './PageHeaderContainer'; import ProjectsListContainer from './ProjectsListContainer'; @@ -29,32 +29,27 @@ import { parseUrlQuery } from '../store/utils'; import { translate } from '../../../helpers/l10n'; import * as utils from '../utils'; import * as storage from '../../../helpers/storage'; -/*:: import type { RawQuery } from '../../../helpers/query'; */ +import { RawQuery } from '../../../helpers/query'; import '../styles.css'; -/*:: -type Props = {| - isFavorite: boolean, - location: { pathname: string, query: RawQuery }, - fetchProjects: (query: string, isFavorite: boolean, organization?: {}) => Promise<*>, - organization?: { key: string }, - router: { - push: ({ pathname: string, query?: {} }) => void, - replace: ({ pathname: string, query?: {} }) => void - }, - currentUser?: { isLoggedIn: boolean } -|}; -*/ - -/*:: -type State = { - query: RawQuery -}; -*/ - -export default class AllProjects extends React.PureComponent { - /*:: props: Props; */ - state /*: State */ = { query: {} }; +interface Props { + isFavorite: boolean; + location: { pathname: string; query: RawQuery }; + fetchProjects: (query: RawQuery, isFavorite: boolean, organization?: {}) => Promise<any>; + organization?: { key: string }; + currentUser?: { isLoggedIn: boolean }; +} + +interface State { + query: RawQuery; +} + +export default class AllProjects extends React.PureComponent<Props, State> { + state: State = { query: {} }; + + static contextTypes = { + router: PropTypes.object.isRequired + }; componentDidMount() { this.handleQueryChange(true); @@ -62,7 +57,7 @@ export default class AllProjects extends React.PureComponent { footer && footer.classList.add('page-footer-with-sidebar'); } - componentDidUpdate(prevProps /*: Props */) { + componentDidUpdate(prevProps: Props) { if (prevProps.location.query !== this.props.location.query) { this.handleQueryChange(false); } @@ -82,23 +77,29 @@ export default class AllProjects extends React.PureComponent { isFiltered = () => Object.keys(this.state.query).some(key => this.state.query[key] != null); getSavedOptions = () => { - const options = {}; + const options: { + sort?: string; + view?: string; + visualization?: string; + } = {}; if (storage.getSort()) { - options.sort = storage.getSort(); + options.sort = storage.getSort() || undefined; } if (storage.getView()) { - options.view = storage.getView(); + options.view = storage.getView() || undefined; } if (storage.getVisualization()) { - options.visualization = storage.getVisualization(); + options.visualization = storage.getVisualization() || undefined; } return options; }; - handlePerspectiveChange = ( - { view, visualization } /*: { view: string, visualization?: string } */ - ) => { - const query /*: { view: ?string, visualization: ?string, sort?: ?string } */ = { + handlePerspectiveChange = ({ view, visualization }: { view: string; visualization?: string }) => { + const query: { + view: string | undefined; + visualization: string | undefined; + sort?: string | undefined; + } = { view: view === 'overall' ? undefined : view, visualization }; @@ -110,7 +111,7 @@ export default class AllProjects extends React.PureComponent { query.sort = (sort.sortDesc ? '-' : '') + utils.SORTING_SWITCH[sort.sortValue]; } } - this.props.router.push({ pathname: this.props.location.pathname, query }); + this.context.router.push({ pathname: this.props.location.pathname, query }); } else { this.updateLocationQuery(query); } @@ -120,28 +121,28 @@ export default class AllProjects extends React.PureComponent { storage.saveVisualization(visualization); }; - handleSortChange = (sort /*: string */, desc /*: boolean */) => { + handleSortChange = (sort: string, desc: boolean) => { const asString = (desc ? '-' : '') + sort; this.updateLocationQuery({ sort: asString }); storage.saveSort(asString); }; - handleQueryChange(initialMount /*: boolean */) { + handleQueryChange(initialMount: boolean) { const query = parseUrlQuery(this.props.location.query); const savedOptions = this.getSavedOptions(); const savedOptionsSet = savedOptions.sort || savedOptions.view || savedOptions.visualization; // if there is no filter, but there are saved preferences in the localStorage if (initialMount && !this.isFiltered() && savedOptionsSet) { - this.props.router.replace({ pathname: this.props.location.pathname, query: savedOptions }); + this.context.router.replace({ pathname: this.props.location.pathname, query: savedOptions }); } else { this.setState({ query }); this.props.fetchProjects(query, this.props.isFavorite, this.props.organization); } } - updateLocationQuery = (newQuery /*: { [string]: ?string } */) => { - this.props.router.push({ + updateLocationQuery = (newQuery: RawQuery) => { + this.context.router.push({ pathname: this.props.location.pathname, query: { ...this.props.location.query, diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts index f2fa86a3f2a..83d5f49ba66 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts @@ -18,8 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; import AllProjects from './AllProjects'; import { fetchProjects } from '../store/actions'; -export default connect(null, { fetchProjects })(withRouter(AllProjects)); +export default connect<null, any, any>(null, { fetchProjects })(AllProjects); diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx index d040e9e7330..f8e73ba7fad 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js +++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx @@ -17,38 +17,30 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; +import * as PropTypes from 'prop-types'; import AllProjectsContainer from './AllProjectsContainer'; import { getCurrentUser } from '../../../store/rootReducer'; import { isFavoriteSet, isAllSet } from '../../../helpers/storage'; import { searchProjects } from '../../../api/components'; -/*:: import type { RawQuery } from '../../../helpers/query'; */ -/*:: -type Props = { - currentUser: { isLoggedIn: boolean }, - location: { query: {} }, - router: { - replace: (location: { pathname?: string, query?: RawQuery }) => void - } -}; -*/ +interface Props { + currentUser: { isLoggedIn: boolean }; + location: { query: { [x: string]: string } }; +} -/*:: -type State = { - shouldBeRedirected?: boolean, - shouldForceSorting?: string -}; -*/ +interface State { + shouldBeRedirected?: boolean; + shouldForceSorting?: string; +} -class DefaultPageSelector extends React.PureComponent { - /*:: props: Props; */ - /*:: state: State; */ +class DefaultPageSelector extends React.PureComponent<Props, State> { + static contextTypes = { + router: PropTypes.object.isRequired + }; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); this.state = {}; } @@ -57,13 +49,13 @@ class DefaultPageSelector extends React.PureComponent { this.defineIfShouldBeRedirected(); } - componentDidUpdate(prevProps /*: Props */) { + componentDidUpdate(prevProps: Props) { if (prevProps.location !== this.props.location) { this.defineIfShouldBeRedirected(); } else if (this.state.shouldBeRedirected === true) { - this.props.router.replace({ ...this.props.location, pathname: '/projects/favorite' }); + this.context.router.replace({ ...this.props.location, pathname: '/projects/favorite' }); } else if (this.state.shouldForceSorting != null) { - this.props.router.replace({ + this.context.router.replace({ ...this.props.location, query: { ...this.props.location.query, @@ -117,8 +109,10 @@ class DefaultPageSelector extends React.PureComponent { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ currentUser: getCurrentUser(state) }); -export default connect(mapStateToProps)(withRouter(DefaultPageSelector)); +export default connect<any, any, any>(mapStateToProps)(DefaultPageSelector); + +export const UnconnectedDefaultPageSelector = DefaultPageSelector; diff --git a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.js b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx index 1eb2241cfb1..d83a4fb3e5e 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.js +++ b/server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { translate } from '../../../helpers/l10n'; export default function EmptyInstance() { diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx index b2e9de9b6e4..372d0613c3e 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx @@ -17,26 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { IndexLink, Link } from 'react-router'; import { translate } from '../../../helpers/l10n'; import { saveAll, saveFavorite } from '../../../helpers/storage'; -/*:: import type { RawQuery } from '../../../helpers/query'; */ +import { RawQuery } from '../../../helpers/query'; -/*:: -type Props = { - user: { - isLoggedIn?: boolean - }, - organization?: { key: string }, - query: RawQuery -}; -*/ - -export default class FavoriteFilter extends React.PureComponent { - /*:: props: Props; */ +interface Props { + user: { isLoggedIn?: boolean }; + organization?: { key: string }; + query: RawQuery; +} +export default class FavoriteFilter extends React.PureComponent<Props> { handleSaveFavorite = () => { if (!this.props.organization) { saveFavorite(); diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.js b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx index d3994b39bdb..515fbf0ae8c 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx @@ -21,8 +21,8 @@ import { connect } from 'react-redux'; import FavoriteFilter from './FavoriteFilter'; import { getCurrentUser } from '../../../store/rootReducer'; -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ user: getCurrentUser(state) }); -export default connect(mapStateToProps)(FavoriteFilter); +export default connect<any, any, any>(mapStateToProps)(FavoriteFilter); diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.js b/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts index bf2a030408b..fc3ad9992c6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts @@ -18,14 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; import AllProjects from './AllProjects'; import { getCurrentUser } from '../../../store/rootReducer'; import { fetchProjects } from '../store/actions'; -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ isFavorite: true, currentUser: getCurrentUser(state) }); -export default connect(mapStateToProps, { fetchProjects })(withRouter(AllProjects)); +export default connect<any, any, any>(mapStateToProps, { fetchProjects })(AllProjects); diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.js b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index f69fbebc834..effc6ad0dbb 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.js +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { Link } from 'react-router'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx index f4a616dc52b..f9fd94bd3dd 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx @@ -17,42 +17,45 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import SearchFilterContainer from '../filters/SearchFilterContainer'; import Tooltip from '../../../components/controls/Tooltip'; import PerspectiveSelect from './PerspectiveSelect'; import ProjectsSortingSelect from './ProjectsSortingSelect'; import { translate } from '../../../helpers/l10n'; -/*:: import type { RawQuery } from '../../../helpers/query'; */ +import { RawQuery } from '../../../helpers/query'; -/*:: -type Props = {| - currentUser?: { isLoggedIn: boolean }, - isFavorite?: boolean, - onPerspectiveChange: ({ view: string, visualization?: string }) => void, - organization?: { key: string }, - projects: Array<*>, - projectsAppState: { loading: boolean, total?: number }, - query: RawQuery, - onSortChange: (sort: string, desc: boolean) => void, - selectedSort: string, - view: string, - visualization?: string -|}; -*/ +interface Props { + currentUser?: { isLoggedIn: boolean }; + isFavorite?: boolean; + onPerspectiveChange: (x: { view: string; visualization?: string }) => void; + organization?: { key: string }; + projects: Array<any>; + projectsAppState: { loading: boolean; total?: number }; + query: RawQuery; + onSortChange: (sort: string, desc: boolean) => void; + selectedSort: string; + view: string; + visualization?: string; +} + +export default function PageHeader(props: Props) { + const { projectsAppState, projects, currentUser, view } = props; + const limitReached = + projects != null && projectsAppState.total != null && projects.length < projectsAppState.total; + const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date'; + + return ( + <header className="page-header projects-topbar-items"> + <PerspectiveSelect + className="projects-topbar-item js-projects-perspective-select" + onChange={props.onPerspectiveChange} + view={props.view} + visualization={props.visualization} + /> -export default function PageHeader(props /*: Props */) { - const renderSortingSelect = () => { - const { projectsAppState, projects, currentUser, view } = props; - const limitReached = - projects != null && - projectsAppState.total != null && - projects.length < projectsAppState.total; - const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date'; - if (view === 'visualizations' && !limitReached) { - return ( + {view === 'visualizations' && !limitReached ? ( <Tooltip overlay={translate('projects.sort.disabled')}> <div className="projects-topbar-item disabled"> <ProjectsSortingSelect @@ -64,29 +67,15 @@ export default function PageHeader(props /*: Props */) { /> </div> </Tooltip> - ); - } - return ( - <ProjectsSortingSelect - className="projects-topbar-item js-projects-sorting-select" - defaultOption={defaultOption} - onChange={props.onSortChange} - selectedSort={props.selectedSort} - view={props.view} - /> - ); - }; - - return ( - <header className="page-header projects-topbar-items"> - <PerspectiveSelect - className="projects-topbar-item js-projects-perspective-select" - onChange={props.onPerspectiveChange} - view={props.view} - visualization={props.visualization} - /> - - {renderSortingSelect()} + ) : ( + <ProjectsSortingSelect + className="projects-topbar-item js-projects-sorting-select" + defaultOption={defaultOption} + onChange={props.onSortChange} + selectedSort={props.selectedSort} + view={props.view} + /> + )} <SearchFilterContainer className="projects-topbar-item projects-topbar-item-search" diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts index 7780da0a705..428e43364d3 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts @@ -21,9 +21,9 @@ import { connect } from 'react-redux'; import PageHeader from './PageHeader'; import { getProjects, getProjectsAppState } from '../../../store/rootReducer'; -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ projects: getProjects(state), projectsAppState: getProjectsAppState(state) }); -export default connect(mapStateToProps)(PageHeader); +export default connect<any, any, any>(mapStateToProps)(PageHeader); diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx index 34c923669db..e1d6e6dddb0 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import { Link } from 'react-router'; import FavoriteFilterContainer from './FavoriteFilterContainer'; import LanguagesFilterContainer from '../filters/LanguagesFilterContainer'; @@ -37,21 +36,18 @@ import SecurityFilter from '../filters/SecurityFilter'; import SizeFilter from '../filters/SizeFilter'; import TagsFilterContainer from '../filters/TagsFilterContainer'; import { translate } from '../../../helpers/l10n'; -/*:: import type { RawQuery } from '../../../helpers/query'; */ +import { RawQuery } from '../../../helpers/query'; -/*:: -type Props = { - isFavorite: boolean, - organization?: { key: string }, - query: RawQuery, - view: string, - visualization: string -}; -*/ +interface Props { + isFavorite: boolean; + organization?: { key: string }; + query: RawQuery; + view: string; + visualization: string; +} -export default function PageSidebar( - { query, isFavorite, organization, view, visualization } /*: Props */ -) { +export default function PageSidebar(props: Props) { + const { query, isFavorite, organization, view, visualization } = props; const isFiltered = Object.keys(query) .filter(key => !['view', 'visualization', 'sort'].includes(key)) .some(key => query[key] != null); @@ -60,12 +56,11 @@ export default function PageSidebar( const pathname = basePathName + (isFavorite ? '/favorite' : ''); const facetProps = { query, isFavorite, organization }; - let linkQuery; + let linkQuery: RawQuery | undefined = undefined; if (view !== 'overall') { linkQuery = { view }; if (view === 'visualizations') { - // $FlowFixMe linkQuery.visualization = visualization; } } diff --git a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx index 2ed398b6e60..c27d1fce861 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx @@ -17,33 +17,32 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import Select from 'react-select'; -import PerspectiveSelectOption from './PerspectiveSelectOption'; +import * as React from 'react'; +import * as Select from 'react-select'; +import PerspectiveSelectOption, { Option } from './PerspectiveSelectOption'; import { translate } from '../../../helpers/l10n'; import { VIEWS, VISUALIZATIONS } from '../utils'; -/*:: -export type Option = { label: string, type: string, value: string }; -*/ - -/*:: -type Props = {| - className?: string, - onChange: ({ view: string, visualization?: string }) => void, - view: string, - visualization?: string -|}; -*/ +interface Props { + className?: string; + onChange: (x: { view: string; visualization?: string }) => void; + view: string; + visualization?: string; +} -export default class PerspectiveSelect extends React.PureComponent { - /*:: options: Array<Option>; */ - /*:: props: Props; */ +export default class PerspectiveSelect extends React.PureComponent<Props> { + handleChange = (option: Option) => { + if (option.type === 'view') { + this.props.onChange({ view: option.value }); + } else if (option.type === 'visualization') { + this.props.onChange({ view: 'visualizations', visualization: option.value }); + } + }; - constructor(props /*: Props */) { - super(props); - this.options = [ + render() { + const { view, visualization } = this.props; + const perspective = view === 'visualizations' ? visualization : view; + const options = [ ...VIEWS.map(opt => ({ type: 'view', value: opt, @@ -55,19 +54,6 @@ export default class PerspectiveSelect extends React.PureComponent { label: translate('projects.visualization', opt) })) ]; - } - - handleChange = (option /*: Option */) => { - if (option.type === 'view') { - this.props.onChange({ view: option.value }); - } else if (option.type === 'visualization') { - this.props.onChange({ view: 'visualizations', visualization: option.value }); - } - }; - - render() { - const { view, visualization } = this.props; - const perspective = view === 'visualizations' ? visualization : view; return ( <div className={this.props.className}> <label>{translate('projects.perspective')}:</label> @@ -76,7 +62,7 @@ export default class PerspectiveSelect extends React.PureComponent { clearable={false} onChange={this.handleChange} optionComponent={PerspectiveSelectOption} - options={this.options} + options={options} searchable={false} value={perspective} /> diff --git a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.js b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.tsx index 6fd3307a98d..0ae8a15e110 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.js +++ b/server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.tsx @@ -17,37 +17,37 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import BubblesIcon from '../../../components/icons-components/BubblesIcon'; import ListIcon from '../../../components/icons-components/ListIcon'; -/*:: import type { Option } from './PerspectiveSelect'; */ -/*:: -type Props = { - option: Option, - children?: Element | Text, - className?: string, - isFocused?: boolean, - onFocus: (Option, MouseEvent) => void, - onSelect: (Option, MouseEvent) => void -}; -*/ +export interface Option { + label: string; + type: string; + value: string; +} -export default class PerspectiveSelectOption extends React.PureComponent { - /*:: props: Props; */ +interface Props { + option: Option; + children?: React.ReactNode; + className?: string; + isFocused?: boolean; + onFocus: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void; + onSelect: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void; +} - handleMouseDown = (event /*: MouseEvent */) => { +export default class PerspectiveSelectOption extends React.PureComponent<Props> { + handleMouseDown = (event: React.SyntheticEvent<HTMLElement>) => { event.preventDefault(); event.stopPropagation(); this.props.onSelect(this.props.option, event); }; - handleMouseEnter = (event /*: MouseEvent */) => { + handleMouseEnter = (event: React.SyntheticEvent<HTMLElement>) => { this.props.onFocus(this.props.option, event); }; - handleMouseMove = (event /*: MouseEvent */) => { + handleMouseMove = (event: React.SyntheticEvent<HTMLElement>) => { if (this.props.isFocused) { return; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx index a47d8a82e33..429d7abe1ae 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx @@ -17,20 +17,38 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { connect } from 'react-redux'; import ProjectCardLeak from './ProjectCardLeak'; import ProjectCardOverall from './ProjectCardOverall'; import { getComponent, getComponentMeasures } from '../../../store/rootReducer'; -function ProjectCard(props /*: { type?: string } */) { +interface Props { + measures?: { [key: string]: string }; + organization?: { key: string }; + project?: { + analysisDate?: string; + key: string; + leakPeriodDate?: string; + name: string; + tags: Array<string>; + isFavorite?: boolean; + organization?: string; + visibility?: string; + }; + type?: string; +} + +function ProjectCard(props: Props) { if (props.type === 'leak') { return <ProjectCardLeak {...props} />; } return <ProjectCardOverall {...props} />; } -export default connect((state, ownProps) => ({ +const mapStateToProps = (state: any, ownProps: any) => ({ project: getComponent(state, ownProps.projectKey), measures: getComponentMeasures(state, ownProps.projectKey) -}))(ProjectCard); +}); + +export default connect(mapStateToProps)(ProjectCard); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js deleted file mode 100644 index 0b6cfab5cde..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import { sortBy } from 'lodash'; -import { connect } from 'react-redux'; -import Tooltip from '../../../components/controls/Tooltip'; -import { getLanguages } from '../../../store/rootReducer'; -import { translate } from '../../../helpers/l10n'; - -class ProjectCardLanguages extends React.PureComponent { - getLanguageName(key) { - if (key === '<null>') { - return translate('unknown'); - } - const language = this.props.languages[key]; - return language != null ? language.name : key; - } - - render() { - const { distribution } = this.props; - - if (distribution == null) { - return null; - } - const parsedLanguages = distribution.split(';').map(item => item.split('=')); - const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l => - this.getLanguageName(l[0]) - ); - - const tooltip = ( - <span> - {finalLanguages.map(language => ( - <span key={language}> - {language} - <br /> - </span> - ))} - </span> - ); - - const languagesText = - finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : ''); - - return ( - <div className="project-card-languages"> - <Tooltip placement="bottom" overlay={tooltip}> - <span>{languagesText}</span> - </Tooltip> - </div> - ); - } -} - -const mapStateToProps = state => ({ - languages: getLanguages(state) -}); - -export default connect(mapStateToProps)(ProjectCardLanguages); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx new file mode 100644 index 00000000000..e676d7378f0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 { sortBy } from 'lodash'; +import Tooltip from '../../../components/controls/Tooltip'; +import { translate } from '../../../helpers/l10n'; + +interface Languages { + [key: string]: { key: string; name: string }; +} + +interface Props { + distribution?: string; + languages: Languages; +} + +export default function ProjectCardLanguages({ distribution, languages }: Props) { + if (distribution == undefined) { + return null; + } + + const parsedLanguages = distribution.split(';').map(item => item.split('=')); + const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l => + getLanguageName(languages, l[0]) + ); + + const tooltip = ( + <span> + {finalLanguages.map(language => ( + <span key={language}> + {language} + <br /> + </span> + ))} + </span> + ); + + const languagesText = + finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : ''); + + return ( + <div className="project-card-languages"> + <Tooltip placement="bottom" overlay={tooltip}> + <span>{languagesText}</span> + </Tooltip> + </div> + ); +} + +function getLanguageName(languages: Languages, key: string): string { + if (key === '<null>') { + return translate('unknown'); + } + const language = languages[key]; + return language != null ? language.name : key; +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts new file mode 100644 index 00000000000..3d0429e6e3c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts @@ -0,0 +1,28 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { connect } from 'react-redux'; +import ProjectCardLanguages from './ProjectCardLanguages'; +import { getLanguages } from '../../../store/rootReducer'; + +const mapStateToProps = (state: any) => ({ + languages: getLanguages(state) +}); + +export default connect<any, any, any>(mapStateToProps)(ProjectCardLanguages); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx index 4c6867a3dd8..aefda0b2ac2 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx @@ -17,12 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import { Link } from 'react-router'; import DateFromNow from '../../../components/intl/DateFromNow'; -import DateTimeFormatter from '../../../components/intl/DateTimeFormatter.tsx'; +import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import ProjectCardQualityGate from './ProjectCardQualityGate'; import ProjectCardLeakMeasures from './ProjectCardLeakMeasures'; import FavoriteContainer from '../../../components/controls/FavoriteContainer'; @@ -31,37 +30,35 @@ import TagsList from '../../../components/tags/TagsList'; import PrivateBadge from '../../../components/common/PrivateBadge'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -/*:: -type Props = { - measures: { [string]: string }, - organization?: { key: string }, +interface Props { + measures?: { [key: string]: string }; + organization?: { key: string }; project?: { - analysisDate?: string, - key: string, - leakPeriodDate?: string, - name: string, - tags: Array<string>, - isFavorite?: boolean, - organization?: string, - visibility?: boolean - } -}; -*/ + analysisDate?: string; + key: string; + leakPeriodDate?: string; + name: string; + tags: Array<string>; + isFavorite?: boolean; + organization?: string; + visibility?: string; + }; +} -export default function ProjectCardLeak({ measures, organization, project } /*: Props */) { - if (project == null) { +export default function ProjectCardLeak({ measures, organization, project }: Props) { + if (project == undefined) { return null; } const isProjectAnalyzed = project.analysisDate != null; const isPrivate = project.visibility === 'private'; - const hasLeakPeriodStart = project.leakPeriodDate != null; + const hasLeakPeriodStart = project.leakPeriodDate != undefined; const hasTags = project.tags.length > 0; - const showOrganization = organization == null && project.organization != null; + const showOrganization = organization == undefined && project.organization != undefined; // check for particular measures because only some measures can be loaded // if coming from visualizations tab - const areProjectMeasuresLoaded = measures != null && measures['new_bugs']; + const areProjectMeasuresLoaded = measures != undefined && measures['new_bugs']; const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed; const className = classNames('boxed-group', 'project-card', { @@ -82,7 +79,7 @@ export default function ProjectCardLeak({ measures, organization, project } /*: )} <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link> </h2> - {displayQualityGate && <ProjectCardQualityGate status={measures['alert_status']} />} + {displayQualityGate && <ProjectCardQualityGate status={measures!['alert_status']} />} <div className="pull-right text-right"> {isPrivate && <PrivateBadge className="spacer-left" tooltipPlacement="left" />} {hasTags && <TagsList tags={project.tags} customClass="spacer-left" />} @@ -91,7 +88,7 @@ export default function ProjectCardLeak({ measures, organization, project } /*: hasLeakPeriodStart && ( <div className="project-card-dates note text-right pull-right"> {hasLeakPeriodStart && ( - <DateFromNow date={project.leakPeriodDate}> + <DateFromNow date={project.leakPeriodDate!}> {fromNow => ( <span className="project-card-leak-date pull-right"> {translateWithParameters('projects.leak_period_x', fromNow)} @@ -100,7 +97,7 @@ export default function ProjectCardLeak({ measures, organization, project } /*: </DateFromNow> )} {isProjectAnalyzed && ( - <DateTimeFormatter date={project.analysisDate}> + <DateTimeFormatter date={project.analysisDate!}> {formattedDate => ( <span> {translateWithParameters('projects.last_analysis_on_x', formattedDate)} diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx index bcd27888e68..353a0d6ccc9 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import Measure from '../../../components/measure/Measure'; import BugIcon from '../../../components/icons-components/BugIcon'; import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon'; @@ -26,14 +25,12 @@ import Rating from '../../../components/ui/Rating'; import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - measures?: { [string]: string } -}; -*/ +interface Props { + measures?: { [key: string]: string }; +} -export default function ProjectCardLeakMeasures({ measures } /*: Props */) { - if (measures == null) { +export default function ProjectCardLeakMeasures({ measures }: Props) { + if (measures == undefined) { return null; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx index 05c35ab14b3..c52c09a3ff6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import { Link } from 'react-router'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; import ProjectCardQualityGate from './ProjectCardQualityGate'; @@ -30,36 +29,36 @@ import TagsList from '../../../components/tags/TagsList'; import PrivateBadge from '../../../components/common/PrivateBadge'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -/*:: -type Props = { - measures: { [string]: string }, - organization?: { key: string }, +interface Props { + measures?: { [key: string]: string }; + organization?: { key: string }; project?: { - analysisDate?: string, - key: string, - name: string, - tags: Array<string>, - isFavorite?: boolean, - organization?: string, - visibility?: boolean - } -}; -*/ + analysisDate?: string; + key: string; + name: string; + tags: Array<string>; + isFavorite?: boolean; + organization?: string; + visibility?: string; + }; +} -export default function ProjectCardOverall({ measures, organization, project } /*: Props */) { - if (project == null) { +export default function ProjectCardOverall({ measures, organization, project }: Props) { + if (project == undefined) { return null; } - const isProjectAnalyzed = project.analysisDate != null; + const isProjectAnalyzed = project.analysisDate != undefined; const isPrivate = project.visibility === 'private'; const hasTags = project.tags.length > 0; - const showOrganization = organization == null && project.organization != null; + const showOrganization = organization == undefined && project.organization != undefined; // check for particular measures because only some measures can be loaded // if coming from visualizations tab const areProjectMeasuresLoaded = - measures != null && measures['reliability_rating'] != null && measures['sqale_rating'] != null; + measures != undefined && + measures['reliability_rating'] != undefined && + measures['sqale_rating'] != undefined; const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed; const className = classNames('boxed-group', 'project-card', { @@ -69,7 +68,7 @@ export default function ProjectCardOverall({ measures, organization, project } / return ( <div data-key={project.key} className={className}> <div className="boxed-group-header clearfix"> - {project.isFavorite != null && ( + {project.isFavorite != undefined && ( <FavoriteContainer className="spacer-right" componentKey={project.key} /> )} <h2 className="project-card-name"> @@ -80,12 +79,12 @@ export default function ProjectCardOverall({ measures, organization, project } / )} <Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link> </h2> - {displayQualityGate && <ProjectCardQualityGate status={measures['alert_status']} />} + {displayQualityGate && <ProjectCardQualityGate status={measures!['alert_status']} />} <div className="pull-right text-right"> {isPrivate && <PrivateBadge className="spacer-left" tooltipPlacement="left" />} {hasTags && <TagsList tags={project.tags} customClass="spacer-left" />} </div> - {isProjectAnalyzed && ( + {project.analysisDate && ( <div className="project-card-dates note text-right"> <DateTimeFormatter date={project.analysisDate}> {formattedDate => ( diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx index 5fabe5635fe..4c5a242a638 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx @@ -17,9 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; -import ProjectCardLanguages from './ProjectCardLanguages'; +import * as React from 'react'; +import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer'; import Measure from '../../../components/measure/Measure'; import Rating from '../../../components/ui/Rating'; import CoverageRating from '../../../components/ui/CoverageRating'; @@ -27,14 +26,12 @@ import DuplicationsRating from '../../../components/ui/DuplicationsRating'; import SizeRating from '../../../components/ui/SizeRating'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - measures?: { [string]: string } -}; -*/ +interface Props { + measures?: { [key: string]: string }; +} -export default function ProjectCardOverallMeasures({ measures } /*: Props */) { - if (measures == null) { +export default function ProjectCardOverallMeasures({ measures }: Props) { + if (measures == undefined) { return null; } @@ -128,7 +125,9 @@ export default function ProjectCardOverallMeasures({ measures } /*: Props */) { /> </div> <div className="project-card-measure-label"> - <ProjectCardLanguages distribution={measures['ncloc_language_distribution']} /> + <ProjectCardLanguagesContainer + distribution={measures['ncloc_language_distribution']} + /> </div> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx index d73a9223c2c..4843d090df9 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx @@ -17,14 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import Level from '../../../components/ui/Level'; import Tooltip from '../../../components/controls/Tooltip'; import { formatMeasure } from '../../../helpers/measures'; import { translateWithParameters } from '../../../helpers/l10n'; -export default function ProjectCardQualityGate({ status } /*: { status?: string } */) { +interface Props { + status?: string; +} + +export default function ProjectCardQualityGate({ status }: Props) { if (!status) { return null; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx index c44468f8555..08db1a07322 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx @@ -17,26 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import ProjectCardContainer from './ProjectCardContainer'; import NoFavoriteProjects from './NoFavoriteProjects'; import EmptyInstance from './EmptyInstance'; import EmptySearch from '../../../components/common/EmptySearch'; -/*:: -type Props = { - projects?: Array<string>, - isFavorite: boolean, - isFiltered: boolean, - organization?: { key: string }, - cardType?: string -}; -*/ - -export default class ProjectsList extends React.PureComponent { - /*:: props: Props; */ +interface Props { + cardType?: string; + isFavorite: boolean; + isFiltered: boolean; + organization?: { key: string }; + projects?: string[]; +} +export default class ProjectsList extends React.PureComponent<Props> { renderNoProjects() { if (this.props.isFavorite && !this.props.isFiltered) { return <NoFavoriteProjects />; @@ -50,7 +45,7 @@ export default class ProjectsList extends React.PureComponent { render() { const { projects } = this.props; - if (projects == null) { + if (projects == undefined) { return null; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts index f326a8f02c7..76c02ecfc10 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts @@ -21,7 +21,9 @@ import { connect } from 'react-redux'; import ProjectsList from './ProjectsList'; import { getProjects, getProjectsAppState } from '../../../store/rootReducer'; -export default connect(state => ({ +const mapStateToProps = (state: any) => ({ projects: getProjects(state), total: getProjectsAppState(state).total -}))(ProjectsList); +}); + +export default connect<any, any, any>(mapStateToProps)(ProjectsList); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts index 542be063a7d..fb296456692 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts @@ -20,9 +20,9 @@ import { connect } from 'react-redux'; import { getProjects, getProjectsAppState } from '../../../store/rootReducer'; import { fetchMoreProjects } from '../store/actions'; -import ProjectsListFooter from './ProjectsListFooter'; +import ListFooter from '../../../components/controls/ListFooter'; -const mapStateToProps = state => { +const mapStateToProps = (state: any) => { const projects = getProjects(state); const appState = getProjectsAppState(state); return { @@ -32,9 +32,9 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = (dispatch, ownProps) => ({ +const mapDispatchToProps = (dispatch: any, ownProps: any) => ({ loadMore: () => dispatch(fetchMoreProjects(ownProps.query, ownProps.isFavorite, ownProps.organization)) }); -export default connect(mapStateToProps, mapDispatchToProps)(ProjectsListFooter); +export default connect(mapStateToProps, mapDispatchToProps)(ListFooter); diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.tsx index 1465efaa30c..eb84d21caed 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.tsx @@ -17,75 +17,51 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { sortBy } from 'lodash'; -import Select from 'react-select'; -import ProjectsSortingSelectOption from './ProjectsSortingSelectOption'; +import * as Select from 'react-select'; +import ProjectsSortingSelectOption, { Option } from './ProjectsSortingSelectOption'; import SortAscIcon from '../../../components/icons-components/SortAscIcon'; import SortDescIcon from '../../../components/icons-components/SortDescIcon'; import Tooltip from '../../../components/controls/Tooltip'; import { translate } from '../../../helpers/l10n'; import { SORTING_METRICS, SORTING_LEAK_METRICS, parseSorting } from '../utils'; -/*:: -export type Option = { label: string, value: string, class?: string, short?: string }; -*/ - -/*:: -type Props = { - className?: string, - onChange: (sort: string, desc: boolean) => void, - selectedSort: string, - view: string, - defaultOption: string -}; -*/ - -/*:: -type State = { - sortValue: string, - sortDesc: boolean -}; -*/ - -export default class ProjectsSortingSelect extends React.PureComponent { - /*:: props: Props; */ - /*:: state: State; */ - - constructor(props /*: Props */) { - super(props); - this.state = parseSorting(props.selectedSort); - } +interface Props { + className?: string; + defaultOption: string; + onChange: (sort: string, desc: boolean) => void; + selectedSort: string; + view: string; +} - componentDidUpdate(prevProps /*: Props */) { - if (prevProps.selectedSort !== this.props.selectedSort) { - this.setState(parseSorting(this.props.selectedSort)); - } - } +export default class ProjectsSortingSelect extends React.PureComponent<Props> { + getSorting = () => parseSorting(this.props.selectedSort); getOptions = () => { const sortMetrics = this.props.view === 'leak' ? SORTING_LEAK_METRICS : SORTING_METRICS; - return sortBy(sortMetrics, opt => (opt.value === this.props.defaultOption ? 0 : 1)).map(( - opt /*: { value: string, class?: string } */ - ) => ({ - value: opt.value, - label: translate('projects.sorting', opt.value), - class: opt.class + return sortBy( + sortMetrics, + option => (option.value === this.props.defaultOption ? 0 : 1) + ).map(option => ({ + value: option.value, + label: translate('projects.sorting', option.value), + class: option.class })); }; - handleDescToggle = (evt /*: Event & { currentTarget: HTMLElement } */) => { - evt.preventDefault(); - evt.currentTarget.blur(); - this.props.onChange(this.state.sortValue, !this.state.sortDesc); + handleDescToggle = (event: React.SyntheticEvent<HTMLAnchorElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + const sorting = this.getSorting(); + this.props.onChange(sorting.sortValue, !sorting.sortDesc); }; - handleSortChange = (option /*: Option */) => - this.props.onChange(option.value, this.state.sortDesc); + handleSortChange = (option: Option) => + this.props.onChange(option.value, this.getSorting().sortDesc); render() { - const { sortDesc } = this.state; + const { sortDesc, sortValue } = this.getSorting(); return ( <div className={this.props.className}> @@ -97,7 +73,7 @@ export default class ProjectsSortingSelect extends React.PureComponent { optionComponent={ProjectsSortingSelectOption} options={this.getOptions()} searchable={false} - value={this.state.sortValue} + value={sortValue} /> <Tooltip overlay={ diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.tsx index aebb6bb478b..91553705be1 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js +++ b/server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.tsx @@ -17,36 +17,37 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; -import classNames from 'classnames'; -/*:: import type { Option } from './ProjectsSortingSelect'; */ +import * as React from 'react'; +import * as classNames from 'classnames'; -/*:: -type Props = { - option: Option, - children?: Element | Text, - className?: string, - isFocused?: boolean, - onFocus: (Option, MouseEvent) => void, - onSelect: (Option, MouseEvent) => void -}; -*/ +export interface Option { + label: string; + value: string; + class?: string; + short?: string; +} -export default class ProjectsSortingSelectOption extends React.PureComponent { - /*:: props: Props; */ +interface Props { + option: Option; + children?: React.ReactNode; + className?: string; + isFocused?: boolean; + onFocus: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void; + onSelect: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void; +} - handleMouseDown = (event /*: MouseEvent */) => { +export default class ProjectsSortingSelectOption extends React.PureComponent<Props> { + handleMouseDown = (event: React.SyntheticEvent<HTMLElement>) => { event.preventDefault(); event.stopPropagation(); this.props.onSelect(this.props.option, event); }; - handleMouseEnter = (event /*: MouseEvent */) => { + handleMouseEnter = (event: React.SyntheticEvent<HTMLElement>) => { this.props.onFocus(this.props.option, event); }; - handleMouseMove = (event /*: MouseEvent */) => { + handleMouseMove = (event: React.SyntheticEvent<HTMLElement>) => { if (this.props.isFocused) { return; } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx new file mode 100644 index 00000000000..fd16bdac37d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx @@ -0,0 +1,180 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../ProjectsListContainer', () => ({ + default: function ProjectsListContainer() { + return null; + } +})); + +jest.mock('../ProjectsListFooterContainer', () => ({ + default: function ProjectsListFooterContainer() { + return null; + } +})); +jest.mock('../PageHeaderContainer', () => ({ + default: function PageHeaderContainer() { + return null; + } +})); + +jest.mock('../PageSidebar', () => ({ + default: function PageSidebar() { + return null; + } +})); + +jest.mock('../../../../helpers/storage', () => ({ + getSort: () => null, + getView: jest.fn(() => null), + getVisualization: () => null, + saveSort: jest.fn(), + saveView: jest.fn(), + saveVisualization: jest.fn() +})); + +import * as React from 'react'; +import { mount, shallow } from 'enzyme'; +import AllProjects from '../AllProjects'; +import { getView, saveSort, saveView, saveVisualization } from '../../../../helpers/storage'; + +beforeEach(() => { + (getView as jest.Mock<any>).mockImplementation(() => null); + (saveSort as jest.Mock<any>).mockClear(); + (saveView as jest.Mock<any>).mockClear(); + (saveVisualization as jest.Mock<any>).mockClear(); +}); + +it('renders', () => { + const wrapper = shallow( + <AllProjects + fetchProjects={jest.fn()} + isFavorite={false} + location={{ pathname: '/projects', query: {} }} + />, + { context: { router: {} } } + ); + expect(wrapper).toMatchSnapshot(); + wrapper.setState({ query: { view: 'visualizations' } }); + expect(wrapper).toMatchSnapshot(); +}); + +it('fetches projects', () => { + const fetchProjects = jest.fn(); + mountRender({ fetchProjects }); + expect(fetchProjects).lastCalledWith( + { + coverage: null, + duplications: null, + gate: null, + languages: null, + maintainability: null, + new_coverage: null, + new_duplications: null, + new_lines: null, + new_maintainability: null, + new_reliability: null, + new_security: null, + reliability: null, + search: null, + security: null, + size: null, + sort: null, + tags: null, + view: undefined, + visualization: null + }, + false, + undefined + ); +}); + +it('redirects to the saved search', () => { + (getView as jest.Mock<any>).mockImplementation(() => 'leak'); + const replace = jest.fn(); + mountRender({}, jest.fn(), replace); + expect(replace).lastCalledWith({ pathname: '/projects', query: { view: 'leak' } }); +}); + +it('changes sort', () => { + const push = jest.fn(); + const wrapper = mountRender({}, push); + wrapper.find('PageHeaderContainer').prop<Function>('onSortChange')('size', false); + expect(push).lastCalledWith({ pathname: '/projects', query: { sort: 'size' } }); + expect(saveSort).lastCalledWith('size'); +}); + +it('changes perspective to leak', () => { + const push = jest.fn(); + const wrapper = mountRender({}, push); + wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({ view: 'leak' }); + expect(push).lastCalledWith({ + pathname: '/projects', + query: { view: 'leak', visualization: undefined } + }); + expect(saveSort).lastCalledWith(undefined); + expect(saveView).lastCalledWith('leak'); + expect(saveVisualization).lastCalledWith(undefined); +}); + +it('updates sorting when changing perspective from leak', () => { + const push = jest.fn(); + const wrapper = mountRender( + { location: { pathname: '/projects', query: { sort: 'new_coverage', view: 'leak' } } }, + push + ); + wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({ + view: undefined + }); + expect(push).lastCalledWith({ + pathname: '/projects', + query: { sort: 'coverage', view: undefined, visualization: undefined } + }); + expect(saveSort).lastCalledWith('coverage'); + expect(saveView).lastCalledWith(undefined); + expect(saveVisualization).lastCalledWith(undefined); +}); + +it('changes perspective to risk visualization', () => { + const push = jest.fn(); + const wrapper = mountRender({}, push); + wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({ + view: 'visualizations', + visualization: 'risk' + }); + expect(push).lastCalledWith({ + pathname: '/projects', + query: { view: 'visualizations', visualization: 'risk' } + }); + expect(saveSort).lastCalledWith(undefined); + expect(saveView).lastCalledWith('visualizations'); + expect(saveVisualization).lastCalledWith('risk'); +}); + +function mountRender(props: any = {}, push: Function = jest.fn(), replace: Function = jest.fn()) { + return mount( + <AllProjects + fetchProjects={jest.fn()} + isFavorite={false} + location={{ pathname: '/projects', query: {} }} + {...props} + />, + { context: { router: { push, replace } } } + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx new file mode 100644 index 00000000000..a7af9fce1e6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx @@ -0,0 +1,89 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../AllProjectsContainer', () => ({ + default: function AllProjectsContainer() { + return null; + } +})); + +jest.mock('../../../../helpers/storage', () => ({ + isFavoriteSet: jest.fn(), + isAllSet: jest.fn() +})); + +jest.mock('../../../../api/components', () => ({ + searchProjects: jest.fn() +})); + +import * as React from 'react'; +import { mount } from 'enzyme'; +import { UnconnectedDefaultPageSelector } from '../DefaultPageSelector'; +import { doAsync } from '../../../../helpers/testUtils'; + +const isFavoriteSet = require('../../../../helpers/storage').isFavoriteSet as jest.Mock<any>; +const isAllSet = require('../../../../helpers/storage').isAllSet as jest.Mock<any>; +const searchProjects = require('../../../../api/components').searchProjects as jest.Mock<any>; + +beforeEach(() => { + isFavoriteSet.mockImplementation(() => false).mockClear(); + isAllSet.mockImplementation(() => false).mockClear(); +}); + +it('shows all projects with existing filter', () => { + const replace = jest.fn(); + mountRender(undefined, { size: '1' }, replace); + expect(replace).not.toBeCalled(); +}); + +it('shows all projects sorted by analysis date for anonymous', () => { + const replace = jest.fn(); + mountRender({ isLoggedIn: false }, undefined, replace); + expect(replace).lastCalledWith({ query: { sort: '-analysis_date' } }); +}); + +it('shows favorite projects', () => { + isFavoriteSet.mockImplementation(() => true); + const replace = jest.fn(); + mountRender(undefined, undefined, replace); + expect(replace).lastCalledWith({ pathname: '/projects/favorite', query: {} }); +}); + +it('shows all projects', () => { + isAllSet.mockImplementation(() => true); + const replace = jest.fn(); + mountRender(undefined, undefined, replace); + expect(replace).not.toBeCalled(); +}); + +it('fetches favorites', () => { + searchProjects.mockImplementation(() => Promise.resolve({ paging: { total: 3 } })); + const replace = jest.fn(); + mountRender(undefined, undefined, replace); + return doAsync().then(() => { + expect(searchProjects).toHaveBeenLastCalledWith({ filter: 'isFavorite', ps: 1 }); + expect(replace).toBeCalledWith({ pathname: '/projects/favorite', query: {} }); + }); +}); + +function mountRender(user: any = { isLoggedIn: true }, query: any = {}, replace: any = jest.fn()) { + return mount(<UnconnectedDefaultPageSelector currentUser={user} location={{ query }} />, { + context: { router: { replace } } + }); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx new file mode 100644 index 00000000000..2f9fe7f31f8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import EmptyInstance from '../EmptyInstance'; + +it('renders', () => { + expect(shallow(<EmptyInstance />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx new file mode 100644 index 00000000000..49e81f03b90 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../../../../helpers/storage', () => ({ + saveAll: jest.fn(), + saveFavorite: jest.fn() +})); + +import * as React from 'react'; +import { shallow } from 'enzyme'; +import FavoriteFilter from '../FavoriteFilter'; +import { saveAll, saveFavorite } from '../../../../helpers/storage'; +import { click } from '../../../../helpers/testUtils'; + +const user = { isLoggedIn: true }; +const query = { size: 1 }; + +beforeEach(() => { + (saveAll as jest.Mock<any>).mockClear(); + (saveFavorite as jest.Mock<any>).mockClear(); +}); + +it('renders for logged in user', () => { + expect(shallow(<FavoriteFilter query={query} user={user} />)).toMatchSnapshot(); +}); + +it('saves last selection', () => { + const wrapper = shallow(<FavoriteFilter query={query} user={user} />); + click(wrapper.find('#favorite-projects')); + expect(saveFavorite).toBeCalled(); + click(wrapper.find('#all-projects')); + expect(saveAll).toBeCalled(); +}); + +it('handles organization', () => { + expect( + shallow(<FavoriteFilter organization={{ key: 'org' }} query={query} user={user} />) + ).toMatchSnapshot(); +}); + +it('does not save last selection with organization', () => { + const wrapper = shallow( + <FavoriteFilter organization={{ key: 'org' }} query={query} user={user} /> + ); + click(wrapper.find('#favorite-projects')); + expect(saveFavorite).not.toBeCalled(); + click(wrapper.find('#all-projects')); + expect(saveAll).not.toBeCalled(); +}); + +it('does not render for anonymous', () => { + expect(shallow(<FavoriteFilter query={query} user={{ isLoggedIn: false }} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx new file mode 100644 index 00000000000..a11013b31d7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NoFavoriteProjects from '../NoFavoriteProjects'; + +it('renders', () => { + expect(shallow(<NoFavoriteProjects />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx index 0a9b1b2aca0..48e9a86b3b2 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx @@ -17,31 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import PageHeader from '../PageHeader'; it('should render correctly', () => { - expect( - shallow(<PageHeader query={{ search: 'test' }} projectsAppState={{ total: 12 }} />) - ).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); it('should render correctly while loading', () => { - expect( - shallow( - <PageHeader - query={{ search: '' }} - isFavorite={true} - projectsAppState={{ loading: true, total: 2 }} - /> - ) - ).toMatchSnapshot(); + expect(shallowRender({ projectsAppState: { loading: true, total: 2 } })).toMatchSnapshot(); }); it('should not render projects total', () => { expect( - shallow(<PageHeader projectsAppState={{}} query={{ search: '' }} />) + shallowRender({ projectsAppState: {} }) .find('#projects-total') .exists() ).toBeFalsy(); @@ -49,38 +39,47 @@ it('should not render projects total', () => { it('should render disabled sorting options for visualizations', () => { expect( - shallow( - <PageHeader - open={true} - projectsAppState={{}} - view="visualizations" - visualization="coverage" - /> - ) + shallowRender({ + open: true, + projectsAppState: {}, + view: 'visualizations', + visualization: 'coverage' + }) ).toMatchSnapshot(); }); it('should render switch the default sorting option for anonymous users', () => { expect( - shallow( - <PageHeader - currentUser={{ isLoggedIn: true }} - open={true} - projectsAppState={{}} - view="overall" - visualization="risk" - /> - ).find('ProjectsSortingSelect') + shallowRender({ + currentUser: { isLoggedIn: true }, + open: true, + projectsAppState: {}, + visualization: 'risk' + }).find('ProjectsSortingSelect') ).toMatchSnapshot(); + expect( - shallow( - <PageHeader - currentUser={{ isLoggedIn: false }} - open={true} - projectsAppState={{}} - view="leak" - visualization="risk" - /> - ).find('ProjectsSortingSelect') + shallowRender({ + currentUser: { isLoggedIn: false }, + open: true, + projectsAppState: {}, + view: 'leak', + visualization: 'risk' + }).find('ProjectsSortingSelect') ).toMatchSnapshot(); }); + +function shallowRender(props?: any) { + return shallow( + <PageHeader + onPerspectiveChange={jest.fn()} + onSortChange={jest.fn()} + projects={[]} + projectsAppState={{ loading: false, total: 12 }} + query={{ search: 'test' }} + selectedSort="size" + view="overall" + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx index fd396c25efe..6d071b47da5 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import PageSidebar from '../PageSidebar'; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.tsx index a1a49c3ea84..8cb10d0fe2e 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.tsx @@ -17,17 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import PerspectiveSelect from '../PerspectiveSelect'; it('should render correctly', () => { - expect(shallow(<PerspectiveSelect view="overall" />)).toMatchSnapshot(); + expect(shallow(<PerspectiveSelect onChange={jest.fn()} view="overall" />)).toMatchSnapshot(); }); it('should render with coverage selected', () => { expect( - shallow(<PerspectiveSelect view="visualizations" visualization="coverage" />) + shallow( + <PerspectiveSelect onChange={jest.fn()} view="visualizations" visualization="coverage" /> + ) ).toMatchSnapshot(); }); @@ -35,9 +37,9 @@ it('should handle perspective change correctly', () => { const onChange = jest.fn(); const instance = shallow( <PerspectiveSelect view="visualizations" visualization="coverage" onChange={onChange} /> - ).instance(); - instance.handleChange({ value: 'overall', type: 'view' }); - instance.handleChange({ value: 'leak', type: 'view' }); - instance.handleChange({ value: 'coverage', type: 'visualization' }); + ).instance() as PerspectiveSelect; + instance.handleChange({ label: 'overall', value: 'overall', type: 'view' }); + instance.handleChange({ label: 'leak', value: 'leak', type: 'view' }); + instance.handleChange({ label: 'coverage', value: 'coverage', type: 'visualization' }); expect(onChange.mock.calls).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.tsx new file mode 100644 index 00000000000..f0aaeeffbbf --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.tsx @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 { shallow } from 'enzyme'; +import PerspectiveSelectOption from '../PerspectiveSelectOption'; + +it('should render correctly for a view', () => { + expect( + shallow( + <PerspectiveSelectOption + onFocus={jest.fn()} + onSelect={jest.fn()} + option={{ value: 'overall', type: 'view', label: 'Overall' }}> + Overall + </PerspectiveSelectOption> + ) + ).toMatchSnapshot(); +}); + +it('should render correctly for a visualization', () => { + expect( + shallow( + <PerspectiveSelectOption + onFocus={jest.fn()} + onSelect={jest.fn()} + option={{ value: 'coverage', type: 'visualization', label: 'Coverage' }}> + Coverage + </PerspectiveSelectOption> + ) + ).toMatchSnapshot(); +}); + +it('selects option', () => { + const onSelect = jest.fn(); + const option = { value: 'coverage', type: 'visualization', label: 'Coverage' }; + const wrapper = shallow( + <PerspectiveSelectOption onFocus={jest.fn()} onSelect={onSelect} option={option} /> + ); + const event = { stopPropagation() {}, preventDefault() {} }; + wrapper.simulate('mousedown', event); + expect(onSelect).toBeCalledWith(option, event); +}); + +it('focuses option', () => { + const onFocus = jest.fn(); + const option = { value: 'coverage', type: 'visualization', label: 'Coverage' }; + const wrapper = shallow( + <PerspectiveSelectOption onFocus={onFocus} onSelect={jest.fn()} option={option} /> + ); + const event = { stopPropagation() {}, preventDefault() {} }; + + wrapper.simulate('mouseenter', event); + expect(onFocus).toBeCalledWith(option, event); + + onFocus.mockClear(); + wrapper.simulate('mousemove', event); + expect(onFocus).toBeCalledWith(option, event); + + onFocus.mockClear(); + wrapper.setProps({ isFocused: true }); + wrapper.simulate('mousemove', event); + expect(onFocus).not.toBeCalled(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx new file mode 100644 index 00000000000..76b724f3bce --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import ProjectCardLanguages from '../ProjectCardLanguages'; + +const languages = { + java: { key: 'java', name: 'Java' }, + js: { key: 'js', name: 'JavaScript' } +}; + +it('renders', () => { + expect( + shallow(<ProjectCardLanguages distribution="java=137;js=15" languages={languages} />) + ).toMatchSnapshot(); +}); + +it('sorts languages', () => { + expect( + shallow(<ProjectCardLanguages distribution="java=13;js=152" languages={languages} />) + ).toMatchSnapshot(); +}); + +it('handles unknown languages', () => { + expect( + shallow(<ProjectCardLanguages distribution="java=13;cpp=18" languages={languages} />) + ).toMatchSnapshot(); + + expect( + shallow(<ProjectCardLanguages distribution="java=13;<null>=18" languages={languages} />) + ).toMatchSnapshot(); +}); + +it('does not render', () => { + expect(shallow(<ProjectCardLanguages languages={languages} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx index 7be476e780e..41b383216dc 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectCardLeak from '../ProjectCardLeak'; @@ -32,11 +32,11 @@ const MEASURES = { alert_status: 'OK', reliability_rating: '1.0', sqale_rating: '1.0', - new_bugs: 12 + new_bugs: '12' }; it('should display analysis date and leak start date', () => { - const card = shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={PROJECT} />); + const card = shallow(<ProjectCardLeak measures={MEASURES} project={PROJECT} />); expect(card.find('.project-card-dates').exists()).toBeTruthy(); expect(card.find('.project-card-dates').find('DateFromNow')).toHaveLength(1); expect(card.find('.project-card-dates').find('DateTimeFormatter')).toHaveLength(1); @@ -44,14 +44,14 @@ it('should display analysis date and leak start date', () => { it('should not display analysis date or leak start date', () => { const project = { ...PROJECT, analysisDate: undefined }; - const card = shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={project} />); + const card = shallow(<ProjectCardLeak measures={MEASURES} project={project} />); expect(card.find('.project-card-dates').exists()).toBeFalsy(); }); it('should display loading', () => { - const measures = { ...MEASURES, new_bugs: undefined }; + const measures = { alert_status: 'OK', reliability_rating: '1.0', sqale_rating: '1.0' }; expect( - shallow(<ProjectCardLeak type="leak" measures={measures} project={PROJECT} />) + shallow(<ProjectCardLeak measures={measures} project={PROJECT} />) .find('.boxed-group') .hasClass('boxed-group-loading') ).toBeTruthy(); @@ -60,7 +60,7 @@ it('should display loading', () => { it('should display tags', () => { const project = { ...PROJECT, tags: ['foo', 'bar'] }; expect( - shallow(<ProjectCardLeak type="leak" project={project} />) + shallow(<ProjectCardLeak project={project} />) .find('TagsList') .exists() ).toBeTruthy(); @@ -69,14 +69,12 @@ it('should display tags', () => { it('should private badge', () => { const project = { ...PROJECT, visibility: 'private' }; expect( - shallow(<ProjectCardLeak type="leak" project={project} />) + shallow(<ProjectCardLeak project={project} />) .find('PrivateBadge') .exists() ).toBeTruthy(); }); it('should display the leak measures and quality gate', () => { - expect( - shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={PROJECT} />) - ).toMatchSnapshot(); + expect(shallow(<ProjectCardLeak measures={MEASURES} project={PROJECT} />)).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx index 5003e421267..5284ac031d1 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx @@ -17,38 +17,37 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectCardLeakMeasures from '../ProjectCardLeakMeasures'; -const measures = { - alert_status: 'ERROR', - new_reliability_rating: '1.0', - new_bugs: '8', - new_security_rating: '2.0', - new_vulnerabilities: '2', - new_maintainability_rating: '1.0', - new_code_smells: '0', - new_coverage: '26.55', - new_duplicated_lines_density: '0.55', - new_lines: '87' -}; - it('should render correctly with all data', () => { + const measures = { + alert_status: 'ERROR', + new_reliability_rating: '1.0', + new_bugs: '8', + new_security_rating: '2.0', + new_vulnerabilities: '2', + new_maintainability_rating: '1.0', + new_code_smells: '0', + new_coverage: '26.55', + new_duplicated_lines_density: '0.55', + new_lines: '87' + }; const wrapper = shallow(<ProjectCardLeakMeasures measures={measures} />); expect(wrapper).toMatchSnapshot(); }); it('should render no data style new coverage, new duplications and new lines', () => { - const wrapper = shallow( - <ProjectCardLeakMeasures - measures={{ - ...measures, - new_coverage: undefined, - new_duplicated_lines_density: undefined, - new_lines: undefined - }} - /> - ); + const measures = { + alert_status: 'ERROR', + new_reliability_rating: '1.0', + new_bugs: '8', + new_security_rating: '2.0', + new_vulnerabilities: '2', + new_maintainability_rating: '1.0', + new_code_smells: '0' + }; + const wrapper = shallow(<ProjectCardLeakMeasures measures={measures} />); expect(wrapper).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx index e51b333a35e..a1dcfb897b8 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx @@ -17,13 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectCardOverall from '../ProjectCardOverall'; const PROJECT = { analysisDate: '2017-01-01', - leakPeriodDate: '2016-12-01', key: 'foo', name: 'Foo', tags: [] @@ -32,7 +31,7 @@ const MEASURES = { alert_status: 'OK', reliability_rating: '1.0', sqale_rating: '1.0', - new_bugs: 12 + new_bugs: '12' }; it('should display analysis date (and not leak period) when defined', () => { @@ -49,14 +48,13 @@ it('should display analysis date (and not leak period) when defined', () => { }); it('should display loading', () => { - const measures = { ...MEASURES, sqale_rating: undefined }; expect( shallow(<ProjectCardOverall project={PROJECT} />) .find('.boxed-group') .hasClass('boxed-group-loading') ).toBeTruthy(); expect( - shallow(<ProjectCardOverall measures={measures} project={PROJECT} />) + shallow(<ProjectCardOverall measures={{ sqale_rating: '1.0' }} project={PROJECT} />) .find('.boxed-group') .hasClass('boxed-group-loading') ).toBeTruthy(); @@ -83,7 +81,7 @@ it('should display tags', () => { it('should private badge', () => { const project = { ...PROJECT, visibility: 'private' }; expect( - shallow(<ProjectCardOverall type="overall" project={project} />) + shallow(<ProjectCardOverall project={project} />) .find('PrivateBadge') .exists() ).toBeTruthy(); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx index 7ee12ea5dfa..ef032d7568f 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectCardOverallMeasures from '../ProjectCardOverallMeasures'; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx new file mode 100644 index 00000000000..1a0f549c763 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import ProjectCardQualityGate from '../ProjectCardQualityGate'; + +it('renders', () => { + expect(shallow(<ProjectCardQualityGate status="ERROR" />)).toMatchSnapshot(); +}); + +it('does not render', () => { + expect(shallow(<ProjectCardQualityGate />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx new file mode 100644 index 00000000000..d15a866c1cb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import ProjectsList from '../ProjectsList'; + +it('renders', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('does not render without projects', () => { + expect(shallow(<ProjectsList isFavorite={false} isFiltered={false} />)).toMatchSnapshot(); +}); + +it('renders different types of "no projects"', () => { + expect(shallowRender({ projects: [] })).toMatchSnapshot(); + expect(shallowRender({ projects: [], isFiltered: true })).toMatchSnapshot(); + expect(shallowRender({ projects: [], isFavorite: true })).toMatchSnapshot(); +}); + +function shallowRender(props?: any) { + return shallow( + <ProjectsList + cardType="overall" + isFavorite={false} + isFiltered={false} + projects={['foo', 'bar']} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx index 4906bf180b2..649d9bec8e3 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx @@ -17,13 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import ProjectsSortingSelect from '../ProjectsSortingSelect'; +import { click } from '../../../../helpers/testUtils'; it('should render correctly for overall view', () => { expect( - shallow(<ProjectsSortingSelect selectedSort="name" view="overall" defaultOption="name" />) + shallow( + <ProjectsSortingSelect + defaultOption="name" + onChange={jest.fn()} + selectedSort="name" + view="overall" + /> + ) ).toMatchSnapshot(); }); @@ -31,9 +39,10 @@ it('should render correctly for leak view', () => { expect( shallow( <ProjectsSortingSelect + defaultOption="analysis_date" + onChange={jest.fn()} selectedSort="new_coverage" view="leak" - defaultOption="analysis_date" /> ) ).toMatchSnapshot(); @@ -42,7 +51,40 @@ it('should render correctly for leak view', () => { it('should handle the descending sort direction', () => { expect( shallow( - <ProjectsSortingSelect selectedSort="-vulnerability" view="overall" defaultOption="name" /> + <ProjectsSortingSelect + defaultOption="name" + onChange={jest.fn()} + selectedSort="-vulnerability" + view="overall" + /> ) ).toMatchSnapshot(); }); + +it('changes sorting', () => { + const onChange = jest.fn(); + const instance = shallow( + <ProjectsSortingSelect + defaultOption="name" + onChange={onChange} + selectedSort="-vulnerabilities" + view="overall" + /> + ).instance() as ProjectsSortingSelect; + instance.handleSortChange({ label: 'size', value: 'size' }); + expect(onChange).toBeCalledWith('size', true); +}); + +it('reverses sorting', () => { + const onChange = jest.fn(); + const wrapper = shallow( + <ProjectsSortingSelect + defaultOption="name" + onChange={onChange} + selectedSort="-size" + view="overall" + /> + ); + click(wrapper.find('a')); + expect(onChange).toBeCalledWith('size', false); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelectOption-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelectOption-test.tsx new file mode 100644 index 00000000000..22d8d49653b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelectOption-test.tsx @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 { shallow } from 'enzyme'; +import ProjectsSortingSelectOption from '../ProjectsSortingSelectOption'; + +it('renders', () => { + expect( + shallow( + <ProjectsSortingSelectOption + onFocus={jest.fn()} + onSelect={jest.fn()} + option={{ label: 'Size', value: 'size' }}> + Size + </ProjectsSortingSelectOption> + ) + ).toMatchSnapshot(); +}); + +it('renders short', () => { + expect( + shallow( + <ProjectsSortingSelectOption + onFocus={jest.fn()} + onSelect={jest.fn()} + option={{ label: 'Size', short: 'Short', value: 'size' }}> + Size + </ProjectsSortingSelectOption> + ) + ).toMatchSnapshot(); +}); + +it('selects option', () => { + const onSelect = jest.fn(); + const option = { value: 'coverage', type: 'visualization', label: 'Coverage' }; + const wrapper = shallow( + <ProjectsSortingSelectOption onFocus={jest.fn()} onSelect={onSelect} option={option} /> + ); + const event = { stopPropagation() {}, preventDefault() {} }; + wrapper.simulate('mousedown', event); + expect(onSelect).toBeCalledWith(option, event); +}); + +it('focuses option', () => { + const onFocus = jest.fn(); + const option = { value: 'coverage', type: 'visualization', label: 'Coverage' }; + const wrapper = shallow( + <ProjectsSortingSelectOption onFocus={onFocus} onSelect={jest.fn()} option={option} /> + ); + const event = { stopPropagation() {}, preventDefault() {} }; + + wrapper.simulate('mouseenter', event); + expect(onFocus).toBeCalledWith(option, event); + + onFocus.mockClear(); + wrapper.simulate('mousemove', event); + expect(onFocus).toBeCalledWith(option, event); + + onFocus.mockClear(); + wrapper.setProps({ isFocused: true }); + wrapper.simulate('mousemove', event); + expect(onFocus).not.toBeCalled(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap new file mode 100644 index 00000000000..70ce07e9dab --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap @@ -0,0 +1,155 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="layout-page projects-page" +> + <HelmetWrapper + encodeSpecialCharacters={true} + title="projects.page" + /> + <div + className="layout-page-side-outer" + > + <div + className="layout-page-side projects-page-side" + style={ + Object { + "top": 30, + } + } + > + <div + className="layout-page-side-inner" + > + <div + className="layout-page-filters" + > + <PageSidebar + isFavorite={false} + query={Object {}} + view="overall" + visualization="risk" + /> + </div> + </div> + </div> + </div> + <div + className="layout-page-main projects-page-content" + > + <div + className="layout-page-header-panel layout-page-main-header" + > + <div + className="layout-page-header-panel-inner layout-page-main-header-inner" + > + <div + className="layout-page-main-inner" + > + <PageHeaderContainer + isFavorite={false} + onPerspectiveChange={[Function]} + onSortChange={[Function]} + query={Object {}} + selectedSort="name" + view="overall" + visualization="risk" + /> + </div> + </div> + </div> + <div + className="layout-page-main-inner" + > + <ProjectsListContainer + cardType="overall" + isFavorite={false} + isFiltered={false} + /> + <ProjectsListFooterContainer + isFavorite={false} + query={Object {}} + /> + </div> + </div> +</div> +`; + +exports[`renders 2`] = ` +<div + className="layout-page projects-page" +> + <HelmetWrapper + encodeSpecialCharacters={true} + title="projects.page" + /> + <div + className="layout-page-side-outer" + > + <div + className="layout-page-side projects-page-side" + style={ + Object { + "top": 30, + } + } + > + <div + className="layout-page-side-inner" + > + <div + className="layout-page-filters" + > + <PageSidebar + isFavorite={false} + query={ + Object { + "view": "visualizations", + } + } + view="visualizations" + visualization="risk" + /> + </div> + </div> + </div> + </div> + <div + className="layout-page-main projects-page-content" + > + <div + className="layout-page-header-panel layout-page-main-header" + > + <div + className="layout-page-header-panel-inner layout-page-main-header-inner" + > + <div + className="layout-page-main-inner" + > + <PageHeaderContainer + isFavorite={false} + onPerspectiveChange={[Function]} + onSortChange={[Function]} + query={ + Object { + "view": "visualizations", + } + } + selectedSort="name" + view="visualizations" + visualization="risk" + /> + </div> + </div> + </div> + <div + className="layout-page-main-inner" + > + <Connect(Visualizations) + visualization="risk" + /> + </div> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap new file mode 100644 index 00000000000..df541164173 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="projects-empty-list" +> + <h3> + projects.no_projects.empty_instance + </h3> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap new file mode 100644 index 00000000000..411ec45c0b0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`does not render for anonymous 1`] = `null`; + +exports[`handles organization 1`] = ` +<header + className="page-header text-center" +> + <div + className="button-group" + > + <Link + activeClassName="button-active" + className="button" + id="favorite-projects" + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/organizations/org/projects/favorite", + "query": Object { + "size": 1, + }, + } + } + > + my_favorites + </Link> + <IndexLink + activeClassName="button-active" + className="button" + id="all-projects" + onClick={[Function]} + to={ + Object { + "pathname": "/organizations/org/projects", + "query": Object { + "size": 1, + }, + } + } + > + all + </IndexLink> + </div> +</header> +`; + +exports[`renders for logged in user 1`] = ` +<header + className="page-header text-center" +> + <div + className="button-group" + > + <Link + activeClassName="button-active" + className="button" + id="favorite-projects" + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects/favorite", + "query": Object { + "size": 1, + }, + } + } + > + my_favorites + </Link> + <IndexLink + activeClassName="button-active" + className="button" + id="all-projects" + onClick={[Function]} + to={ + Object { + "pathname": "/projects", + "query": Object { + "size": 1, + }, + } + } + > + all + </IndexLink> + </div> +</header> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap new file mode 100644 index 00000000000..3b6652f10f8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="projects-empty-list" +> + <h3> + projects.no_favorite_projects + </h3> + <p + className="big-spacer-top" + > + projects.no_favorite_projects.engagement + </p> + <p + className="big-spacer-top" + > + <Link + className="button" + onlyActiveOnIndex={false} + style={Object {}} + to="/projects/all" + > + projects.explore_projects + </Link> + </p> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap index 17c8eddd16c..f5561258330 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -6,12 +6,17 @@ exports[`should render correctly 1`] = ` > <PerspectiveSelect className="projects-topbar-item js-projects-perspective-select" + onChange={[Function]} + view="overall" /> <ProjectsSortingSelect className="projects-topbar-item js-projects-sorting-select" defaultOption="analysis_date" + onChange={[Function]} + selectedSort="size" + view="overall" /> - <withRouter(SearchFilterContainer) + <SearchFilterContainer className="projects-topbar-item projects-topbar-item-search" query={ Object { @@ -41,17 +46,21 @@ exports[`should render correctly while loading 1`] = ` > <PerspectiveSelect className="projects-topbar-item js-projects-perspective-select" + onChange={[Function]} + view="overall" /> <ProjectsSortingSelect className="projects-topbar-item js-projects-sorting-select" defaultOption="analysis_date" + onChange={[Function]} + selectedSort="size" + view="overall" /> - <withRouter(SearchFilterContainer) + <SearchFilterContainer className="projects-topbar-item projects-topbar-item-search" - isFavorite={true} query={ Object { - "search": "", + "search": "test", } } /> @@ -80,6 +89,7 @@ exports[`should render disabled sorting options for visualizations 1`] = ` > <PerspectiveSelect className="projects-topbar-item js-projects-perspective-select" + onChange={[Function]} view="visualizations" visualization="coverage" /> @@ -93,12 +103,19 @@ exports[`should render disabled sorting options for visualizations 1`] = ` <ProjectsSortingSelect className="js-projects-sorting-select" defaultOption="analysis_date" + onChange={[Function]} + selectedSort="size" view="visualizations" /> </div> </Tooltip> - <withRouter(SearchFilterContainer) + <SearchFilterContainer className="projects-topbar-item projects-topbar-item-search" + query={ + Object { + "search": "test", + } + } /> <div className="projects-topbar-item is-last" @@ -110,6 +127,8 @@ exports[`should render switch the default sorting option for anonymous users 1`] <ProjectsSortingSelect className="projects-topbar-item js-projects-sorting-select" defaultOption="name" + onChange={[Function]} + selectedSort="size" view="overall" /> `; @@ -118,6 +137,8 @@ exports[`should render switch the default sorting option for anonymous users 2`] <ProjectsSortingSelect className="projects-topbar-item js-projects-sorting-select" defaultOption="analysis_date" + onChange={[Function]} + selectedSort="size" view="leak" /> `; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap index d48dbb01e6d..aa067968a43 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap @@ -91,14 +91,13 @@ exports[`should render \`leak\` view correctly 1`] = ` /> <NewLinesFilter isFavorite={false} - property="new_lines" query={ Object { "view": "leak", } } /> - <Connect(withRouter(LanguagesFilter)) + <Connect(LanguagesFilter) isFavorite={false} query={ Object { @@ -106,7 +105,7 @@ exports[`should render \`leak\` view correctly 1`] = ` } } /> - <Connect(withRouter(TagsFilter)) + <Connect(TagsFilter) isFavorite={false} query={ Object { @@ -178,7 +177,6 @@ exports[`should render correctly 1`] = ` /> <CoverageFilter isFavorite={true} - property="coverage" query={ Object { "size": "3", @@ -187,7 +185,6 @@ exports[`should render correctly 1`] = ` /> <DuplicationsFilter isFavorite={true} - property="duplications" query={ Object { "size": "3", @@ -196,14 +193,13 @@ exports[`should render correctly 1`] = ` /> <SizeFilter isFavorite={true} - property="size" query={ Object { "size": "3", } } /> - <Connect(withRouter(LanguagesFilter)) + <Connect(LanguagesFilter) isFavorite={true} query={ Object { @@ -211,7 +207,7 @@ exports[`should render correctly 1`] = ` } } /> - <Connect(withRouter(TagsFilter)) + <Connect(TagsFilter) isFavorite={true} query={ Object { diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.tsx.snap index 7671abced29..7671abced29 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.tsx.snap index 794349a21ca..794349a21ca 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap new file mode 100644 index 00000000000..bf50df65a84 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap @@ -0,0 +1,107 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`does not render 1`] = `null`; + +exports[`handles unknown languages 1`] = ` +<div + className="project-card-languages" +> + <Tooltip + overlay={ + <span> + <span> + cpp + <br /> + </span> + <span> + Java + <br /> + </span> + </span> + } + placement="bottom" + > + <span> + cpp, Java + </span> + </Tooltip> +</div> +`; + +exports[`handles unknown languages 2`] = ` +<div + className="project-card-languages" +> + <Tooltip + overlay={ + <span> + <span> + unknown + <br /> + </span> + <span> + Java + <br /> + </span> + </span> + } + placement="bottom" + > + <span> + unknown, Java + </span> + </Tooltip> +</div> +`; + +exports[`renders 1`] = ` +<div + className="project-card-languages" +> + <Tooltip + overlay={ + <span> + <span> + Java + <br /> + </span> + <span> + JavaScript + <br /> + </span> + </span> + } + placement="bottom" + > + <span> + Java, JavaScript + </span> + </Tooltip> +</div> +`; + +exports[`sorts languages 1`] = ` +<div + className="project-card-languages" +> + <Tooltip + overlay={ + <span> + <span> + JavaScript + <br /> + </span> + <span> + Java + <br /> + </span> + </span> + } + placement="bottom" + > + <span> + JavaScript, Java + </span> + </Tooltip> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap index 91275b64048..cc6ae61aca6 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap @@ -50,7 +50,7 @@ exports[`should display the leak measures and quality gate 1`] = ` measures={ Object { "alert_status": "OK", - "new_bugs": 12, + "new_bugs": "12", "reliability_rating": "1.0", "sqale_rating": "1.0", } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap index bbce939030e..1c98e6df82e 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap @@ -28,8 +28,6 @@ exports[`should render correctly with all data 1`] = ` } /> <Rating - muted={false} - small={false} value="1.0" /> </div> @@ -67,8 +65,6 @@ exports[`should render correctly with all data 1`] = ` } /> <Rating - muted={false} - small={false} value="2.0" /> </div> @@ -106,8 +102,6 @@ exports[`should render correctly with all data 1`] = ` } /> <Rating - muted={false} - small={false} value="1.0" /> </div> @@ -242,8 +236,6 @@ exports[`should render no data style new coverage, new duplications and new line } /> <Rating - muted={false} - small={false} value="1.0" /> </div> @@ -281,8 +273,6 @@ exports[`should render no data style new coverage, new duplications and new line } /> <Rating - muted={false} - small={false} value="2.0" /> </div> @@ -320,8 +310,6 @@ exports[`should render no data style new coverage, new duplications and new line } /> <Rating - muted={false} - small={false} value="1.0" /> </div> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap index 720f0f20129..243bf08788d 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap @@ -47,7 +47,7 @@ exports[`should display the overall measures and quality gate 1`] = ` measures={ Object { "alert_status": "OK", - "new_bugs": 12, + "new_bugs": "12", "reliability_rating": "1.0", "sqale_rating": "1.0", } diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap index 2cf247a1451..d16171e7345 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap @@ -81,8 +81,6 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Rating - muted={false} - small={false} value="1.0" /> </div> @@ -104,8 +102,6 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Rating - muted={false} - small={false} value="1.0" /> </div> @@ -127,8 +123,6 @@ exports[`should render correctly with all data 1`] = ` className="project-card-measure-number" > <Rating - muted={false} - small={false} value="1.0" /> </div> @@ -153,8 +147,6 @@ exports[`should render correctly with all data 1`] = ` className="spacer-right" > <CoverageRating - muted={false} - size="normal" value="88.3" /> </span> @@ -192,8 +184,6 @@ exports[`should render correctly with all data 1`] = ` className="spacer-right" > <DuplicationsRating - muted={false} - small={false} value={9.8} /> </span> @@ -231,8 +221,6 @@ exports[`should render correctly with all data 1`] = ` className="spacer-right" > <SizeRating - muted={false} - small={false} value={2053} /> </span> @@ -274,8 +262,6 @@ exports[`should render ncloc correctly 1`] = ` className="spacer-right" > <SizeRating - muted={false} - small={false} value={16549887} /> </span> diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap new file mode 100644 index 00000000000..54acf190a57 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`does not render 1`] = `null`; + +exports[`renders 1`] = ` +<div + className="project-card-measure project-card-quality-gate spacer-left" +> + <Tooltip + overlay="overview.quality_gate_x.ERROR" + placement="bottom" + > + <div + className="project-card-measure-inner" + > + <Level + level="ERROR" + small={true} + /> + </div> + </Tooltip> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap new file mode 100644 index 00000000000..64edfbdf4da --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`does not render without projects 1`] = `null`; + +exports[`renders 1`] = ` +<div + className="projects-list" +> + <Connect(ProjectCard) + projectKey="foo" + type="overall" + /> + <Connect(ProjectCard) + projectKey="bar" + type="overall" + /> +</div> +`; + +exports[`renders different types of "no projects" 1`] = ` +<div + className="projects-list" +> + <EmptyInstance /> +</div> +`; + +exports[`renders different types of "no projects" 2`] = ` +<div + className="projects-list" +> + <EmptySearch /> +</div> +`; + +exports[`renders different types of "no projects" 3`] = ` +<div + className="projects-list" +> + <NoFavoriteProjects /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.tsx.snap index dfa78f606fc..dfa78f606fc 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelectOption-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelectOption-test.tsx.snap new file mode 100644 index 00000000000..473dc3902ed --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelectOption-test.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="" + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseMove={[Function]} + title="Size" +> + Size +</div> +`; + +exports[`renders short 1`] = ` +<div + className="" + onMouseDown={[Function]} + onMouseEnter={[Function]} + onMouseMove={[Function]} + title="Size" +> + Short +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js deleted file mode 100644 index 701b9439eca..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import FilterContainer from './FilterContainer'; -import FilterHeader from './FilterHeader'; -import CoverageRating from '../../../components/ui/CoverageRating'; -import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings'; -import { translate } from '../../../helpers/l10n'; - -export default class CoverageFilter extends React.PureComponent { - static propTypes = { - className: PropTypes.string, - query: PropTypes.object.isRequired, - isFavorite: PropTypes.bool, - organization: PropTypes.object, - property: PropTypes.string - }; - - static defaultProps = { - property: 'coverage' - }; - - getFacetValueForOption(facet, option) { - const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0', 'NO_DATA']; - return facet[map[option - 1]]; - } - - renderOption(option, selected) { - return ( - <span> - {option < 6 && ( - <CoverageRating - value={getCoverageRatingAverageValue(option)} - size="small" - muted={!selected} - /> - )} - <span className="spacer-left"> - {option < 6 ? ( - getCoverageRatingLabel(option) - ) : ( - <span className="big-spacer-left">{translate('no_data')}</span> - )} - </span> - </span> - ); - } - - render() { - return ( - <FilterContainer - property={this.props.property} - className={this.props.className} - options={[1, 2, 3, 4, 5, 6]} - query={this.props.query} - renderOption={this.renderOption} - isFavorite={this.props.isFavorite} - organization={this.props.organization} - getFacetValueForOption={this.getFacetValueForOption} - highlightUnder={1} - highlightUnderMax={5} - header={<FilterHeader name={translate('metric_domain.Coverage')} />} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx new file mode 100644 index 00000000000..b8784492319 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; +import { Facet } from './Filter'; +import CoverageRating from '../../../components/ui/CoverageRating'; +import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings'; +import { translate } from '../../../helpers/l10n'; + +export interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; +} + +export default function CoverageFilter(props: Props) { + const { property = 'coverage' } = props; + + return ( + <FilterContainer + property={property} + className={props.className} + options={[1, 2, 3, 4, 5, 6]} + query={props.query} + renderOption={renderOption} + isFavorite={props.isFavorite} + organization={props.organization} + getFacetValueForOption={getFacetValueForOption} + highlightUnder={1} + highlightUnderMax={5} + header={<FilterHeader name={translate('metric_domain.Coverage')} />} + /> + ); +} + +function getFacetValueForOption(facet: Facet, option: number): number { + const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0', 'NO_DATA']; + return facet[map[option - 1]]; +} + +function renderOption(option: number, selected: boolean) { + return ( + <span> + {option < 6 && ( + <CoverageRating + value={getCoverageRatingAverageValue(option)} + size="small" + muted={!selected} + /> + )} + <span className="spacer-left"> + {option < 6 ? ( + getCoverageRatingLabel(option) + ) : ( + <span className="big-spacer-left">{translate('no_data')}</span> + )} + </span> + </span> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js deleted file mode 100644 index 3758cb3518d..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import FilterContainer from './FilterContainer'; -import FilterHeader from './FilterHeader'; -import DuplicationsRating from '../../../components/ui/DuplicationsRating'; -import { - getDuplicationsRatingLabel, - getDuplicationsRatingAverageValue -} from '../../../helpers/ratings'; -import { translate } from '../../../helpers/l10n'; - -export default class DuplicationsFilter extends React.PureComponent { - static propTypes = { - className: PropTypes.string, - query: PropTypes.object.isRequired, - isFavorite: PropTypes.bool, - organization: PropTypes.object, - property: PropTypes.string - }; - - static defaultProps = { - property: 'duplications' - }; - - getFacetValueForOption(facet, option) { - const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*', 'NO_DATA']; - return facet[map[option - 1]]; - } - - renderOption(option, selected) { - return ( - <span> - {option < 6 && ( - <DuplicationsRating - value={getDuplicationsRatingAverageValue(option)} - size="small" - muted={!selected} - /> - )} - <span className="spacer-left"> - {option < 6 ? ( - getDuplicationsRatingLabel(option) - ) : ( - <span className="big-spacer-left">{translate('no_data')}</span> - )} - </span> - </span> - ); - } - - render() { - return ( - <FilterContainer - property={this.props.property} - className={this.props.className} - options={[1, 2, 3, 4, 5, 6]} - query={this.props.query} - renderOption={this.renderOption} - isFavorite={this.props.isFavorite} - organization={this.props.organization} - getFacetValueForOption={this.getFacetValueForOption} - highlightUnder={1} - highlightUnderMax={5} - header={<FilterHeader name={translate('metric_domain.Duplications')} />} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx new file mode 100644 index 00000000000..6526aced1b8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; +import DuplicationsRating from '../../../components/ui/DuplicationsRating'; +import { + getDuplicationsRatingLabel, + getDuplicationsRatingAverageValue +} from '../../../helpers/ratings'; +import { translate } from '../../../helpers/l10n'; +import { Facet } from './Filter'; + +export interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; +} + +export default function DuplicationsFilter(props: Props) { + const { property = 'duplications' } = props; + return ( + <FilterContainer + property={property} + className={props.className} + options={[1, 2, 3, 4, 5, 6]} + query={props.query} + renderOption={renderOption} + isFavorite={props.isFavorite} + organization={props.organization} + getFacetValueForOption={getFacetValueForOption} + highlightUnder={1} + highlightUnderMax={5} + header={<FilterHeader name={translate('metric_domain.Duplications')} />} + /> + ); +} + +function getFacetValueForOption(facet: Facet, option: number) { + const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*', 'NO_DATA']; + return facet[map[option - 1]]; +} + +function renderOption(option: number, selected: boolean) { + return ( + <span> + {option < 6 && ( + <DuplicationsRating + value={getDuplicationsRatingAverageValue(option)} + size="small" + muted={!selected} + /> + )} + <span className="spacer-left"> + {option < 6 ? ( + getDuplicationsRatingLabel(option) + ) : ( + <span className="big-spacer-left">{translate('no_data')}</span> + )} + </span> + </span> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/Filter.js b/server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx index 01e7051c7cd..04ac288c5a6 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/Filter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx @@ -17,60 +17,58 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; import { Link } from 'react-router'; import { getFilterUrl } from './utils'; import { formatMeasure } from '../../../helpers/measures'; import { translate } from '../../../helpers/l10n'; -export default class Filter extends React.PureComponent { - static propTypes = { - property: PropTypes.string.isRequired, - className: PropTypes.string, - options: PropTypes.array.isRequired, - query: PropTypes.object.isRequired, - renderOption: PropTypes.func.isRequired, - - value: PropTypes.any, - facet: PropTypes.object, - maxFacetValue: PropTypes.number, - optionClassName: PropTypes.string, - isFavorite: PropTypes.bool, - organization: PropTypes.object, - - getFacetValueForOption: PropTypes.func, - - halfWidth: PropTypes.bool, - highlightUnder: PropTypes.number, - highlightUnderMax: PropTypes.number, - - header: PropTypes.object, - footer: PropTypes.object - }; +export type Option = string | number; +export type Facet = { [x: string]: number }; - static defaultProps = { - halfWidth: false - }; +interface Props { + property: string; + className?: string; + options: Option[]; + query: { [x: string]: any }; + renderOption: (option: Option, isSelected: boolean) => React.ReactNode; + + value?: Option | Option[]; + facet?: Facet; + maxFacetValue?: number; + optionClassName?: string; + isFavorite?: boolean; + organization?: { key: string }; + + getFacetValueForOption?: (facet: Facet, option: Option) => void; + + halfWidth?: boolean; + highlightUnder?: number; + highlightUnderMax?: number; - isSelected(option) { + header?: React.ReactNode; + footer?: React.ReactNode; +} + +export default class Filter extends React.PureComponent<Props> { + isSelected(option: Option): boolean { const { value } = this.props; return Array.isArray(value) ? value.includes(option) : option === value; } - highlightUnder(option) { + highlightUnder(option?: Option): boolean { return ( this.props.highlightUnder != null && - option !== null && + option != null && option > this.props.highlightUnder && (this.props.highlightUnderMax == null || option < this.props.highlightUnderMax) ); } - blurOnClick = (evt /*: Event & { currentTarget: HTMLElement } */) => evt.currentTarget.blur(); + blurOnClick = (event: React.SyntheticEvent<HTMLElement>) => event.currentTarget.blur(); - getPath(option) { + getPath(option: Option) { const { property, value } = this.props; let urlOption; @@ -86,8 +84,8 @@ export default class Filter extends React.PureComponent { return getFilterUrl(this.props, { [property]: urlOption }); } - renderOptionBar(facetValue) { - if (facetValue == null || !this.props.maxFacetValue) { + renderOptionBar(facetValue: number | undefined) { + if (facetValue == undefined || !this.props.maxFacetValue) { return null; } return ( @@ -100,7 +98,7 @@ export default class Filter extends React.PureComponent { ); } - renderOption(option) { + renderOption(option: Option) { const { facet, getFacetValueForOption, value } = this.props; const className = classNames( 'facet', @@ -115,9 +113,13 @@ export default class Filter extends React.PureComponent { const path = this.getPath(option); const facetValue = - facet && getFacetValueForOption ? getFacetValueForOption(facet, option) : null; + facet && getFacetValueForOption ? getFacetValueForOption(facet, option) : undefined; - const isUnderSelectedOption = this.highlightUnder(value) && option > value; + const isUnderSelectedOption = + typeof value === 'number' && + typeof option === 'number' && + this.highlightUnder(value) && + option > value; return ( <Link @@ -139,7 +141,7 @@ export default class Filter extends React.PureComponent { ); } - renderOptions() { + renderOptions = () => { const { options, highlightUnder } = this.props; if (options && options.length > 0) { if (highlightUnder != null) { @@ -166,7 +168,7 @@ export default class Filter extends React.PureComponent { } else { return <div className="search-navigator-facet-empty">{translate('no_results')}</div>; } - } + }; render() { return ( diff --git a/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js b/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts index e70b9b5904e..eee1640b8ad 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts @@ -24,9 +24,10 @@ import { getProjectsAppMaxFacetValue } from '../../../store/rootReducer'; -const mapStateToProps = (state, ownProps) => ({ +const mapStateToProps = (state: any, ownProps: any) => ({ value: ownProps.query[ownProps.property], facet: getProjectsAppFacetByProperty(state, ownProps.property), maxFacetValue: getProjectsAppMaxFacetValue(state) }); -export default connect(mapStateToProps)(Filter); + +export default connect<any, any, any>(mapStateToProps)(Filter); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.tsx b/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.tsx new file mode 100644 index 00000000000..74ee5757733 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.tsx @@ -0,0 +1,34 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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'; + +interface Props { + children?: React.ReactNode; + name: string; +} + +export default function FilterHeader(props: Props) { + return ( + <div className="search-navigator-facet-header projects-facet-header"> + {props.name} + {props.children} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js deleted file mode 100644 index 88ff9bc42f8..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import FilterContainer from './FilterContainer'; -import FilterHeader from './FilterHeader'; -import Rating from '../../../components/ui/Rating'; -import { translate } from '../../../helpers/l10n'; - -export default class IssuesFilter extends React.PureComponent { - static propTypes = { - className: PropTypes.string, - headerDetail: PropTypes.element, - isFavorite: PropTypes.bool, - organization: PropTypes.object, - name: PropTypes.string.isRequired, - property: PropTypes.string.isRequired, - query: PropTypes.object.isRequired - }; - - getFacetValueForOption(facet, option) { - return facet[option]; - } - - renderOption(option, selected) { - return ( - <span> - <Rating value={option} small={true} muted={!selected} /> - {option > 1 && - option < 5 && <span className="note spacer-left">{translate('and_worse')}</span>} - </span> - ); - } - - render() { - return ( - <FilterContainer - property={this.props.property} - className={this.props.className} - options={[1, 2, 3, 4, 5]} - query={this.props.query} - renderOption={this.renderOption} - isFavorite={this.props.isFavorite} - organization={this.props.organization} - getFacetValueForOption={this.getFacetValueForOption} - highlightUnder={1} - header={ - <FilterHeader name={translate('metric_domain', this.props.name)}> - {this.props.headerDetail} - </FilterHeader> - } - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx new file mode 100644 index 00000000000..2a139d6cb00 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; +import Rating from '../../../components/ui/Rating'; +import { translate } from '../../../helpers/l10n'; +import { Facet } from './Filter'; + +interface Props { + className?: string; + headerDetail?: React.ReactNode; + isFavorite?: boolean; + name: string; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; +} + +export default function IssuesFilter(props: Props) { + return ( + <FilterContainer + property={props.property} + className={props.className} + options={[1, 2, 3, 4, 5]} + query={props.query} + renderOption={renderOption} + isFavorite={props.isFavorite} + organization={props.organization} + getFacetValueForOption={getFacetValueForOption} + highlightUnder={1} + header={ + <FilterHeader name={translate('metric_domain', props.name)}> + {props.headerDetail} + </FilterHeader> + } + /> + ); +} + +function getFacetValueForOption(facet: Facet, option: number) { + return facet[option]; +} + +function renderOption(option: number, selected: boolean) { + return ( + <span> + <Rating value={option} small={true} muted={!selected} /> + {option > 1 && + option < 5 && <span className="note spacer-left">{translate('and_worse')}</span>} + </span> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx index 822b80d30f2..b4c7fb562e8 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx @@ -17,55 +17,49 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import { difference, sortBy } from 'lodash'; -import Filter from './Filter'; +import Filter, { Facet } from './Filter'; import FilterHeader from './FilterHeader'; import SearchableFilterFooter from './SearchableFilterFooter'; import SearchableFilterOption from './SearchableFilterOption'; import { getLanguageByKey } from '../../../store/languages/reducer'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - query: {}, - languages: {}, - router: { push: ({ pathname: string, query?: {} }) => void }, - value?: Array<string>, - facet?: {}, - isFavorite?: boolean, - organization?: {}, - maxFacetValue?: number -}; -*/ +interface Languages { + [key: string]: { key: string; name: string }; +} -const LIST_SIZE = 10; +interface Props { + facet?: Facet; + isFavorite?: boolean; + languages: Languages; + maxFacetValue?: number; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; + value?: Array<string>; +} -export default class LanguagesFilter extends React.PureComponent { - /*:: props: Props; */ - property = 'languages'; +const LIST_SIZE = 10; - getSearchOptions( - facet /*: ?{} */, - languages /*: {} */ - ) /*: Array<{ label: string, value: string }> */ { - let languageKeys = Object.keys(languages); - if (facet) { - languageKeys = difference(languageKeys, Object.keys(facet)); +export default class LanguagesFilter extends React.Component<Props> { + getSearchOptions = () => { + let languageKeys = Object.keys(this.props.languages); + if (this.props.facet) { + languageKeys = difference(languageKeys, Object.keys(this.props.facet)); } return languageKeys .slice(0, LIST_SIZE) - .map(key => ({ label: languages[key].name, value: key })); - } + .map(key => ({ label: this.props.languages[key].name, value: key })); + }; - getSortedOptions(facet /*: {} */ = {}) { - return sortBy(Object.keys(facet), [option => -facet[option], option => option]); - } + getSortedOptions = (facet: Facet = {}) => + sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]); - getFacetValueForOption = (facet /*: {} */ = {}, option /*: string */) => facet[option]; + getFacetValueForOption = (facet: Facet = {}, option: string) => facet[option]; - renderOption = (option /*: string */) => ( + renderOption = (option: string) => ( <SearchableFilterOption optionKey={option} option={getLanguageByKey(this.props.languages, option)} @@ -73,9 +67,11 @@ export default class LanguagesFilter extends React.PureComponent { ); render() { + const { property = 'languages' } = this.props; + return ( <Filter - property={this.property} + property={property} options={this.getSortedOptions(this.props.facet)} query={this.props.query} renderOption={this.renderOption} @@ -88,12 +84,11 @@ export default class LanguagesFilter extends React.PureComponent { header={<FilterHeader name={translate('projects.facets.languages')} />} footer={ <SearchableFilterFooter - property={this.property} - query={this.props.query} - options={this.getSearchOptions(this.props.facet, this.props.languages)} isFavorite={this.props.isFavorite} organization={this.props.organization} - router={this.props.router} + options={this.getSearchOptions()} + property={property} + query={this.props.query} /> } /> diff --git a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.js b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts index 8143103d283..8b45c11c36a 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; import LanguagesFilter from './LanguagesFilter'; import { getProjectsAppFacetByProperty, @@ -26,10 +25,10 @@ import { getLanguages } from '../../../store/rootReducer'; -const mapStateToProps = (state, ownProps) => ({ +const mapStateToProps = (state: any, ownProps: any) => ({ languages: getLanguages(state), value: ownProps.query['languages'], facet: getProjectsAppFacetByProperty(state, 'languages'), maxFacetValue: getProjectsAppMaxFacetValue(state) }); -export default connect(mapStateToProps)(withRouter(LanguagesFilter)); +export default connect<any, any, any>(mapStateToProps)(LanguagesFilter); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx index a02a5f70c57..424e3f21ce1 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx @@ -17,9 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import IssuesFilter from './IssuesFilter'; -export default function MaintainabilityFilter(props) { +interface Props { + className?: string; + headerDetail?: React.ReactNode; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} + +export default function MaintainabilityFilter(props: Props) { return <IssuesFilter {...props} name="Maintainability" property="maintainability" />; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.tsx index 24f24ce2706..9d344bf5141 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import CoverageFilter from './CoverageFilter'; +import * as React from 'react'; +import CoverageFilter, { Props } from './CoverageFilter'; -export default function NewCoverageFilter(props) { +export default function NewCoverageFilter(props: Props) { return <CoverageFilter {...props} property="new_coverage" className="leak-facet-box" />; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.tsx index 98754c93080..770b7219d0e 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.tsx @@ -17,9 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import DuplicationsFilter from './DuplicationsFilter'; +import * as React from 'react'; +import DuplicationsFilter, { Props } from './DuplicationsFilter'; -export default function NewDuplicationsFilter(props) { +export default function NewDuplicationsFilter(props: Props) { return <DuplicationsFilter {...props} property="new_duplications" className="leak-facet-box" />; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js deleted file mode 100644 index 2593f06a3a0..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import FilterContainer from './FilterContainer'; -import FilterHeader from './FilterHeader'; -import { translate } from '../../../helpers/l10n'; -import { getSizeRatingLabel } from '../../../helpers/ratings'; - -export default class NewLinesFilter extends React.PureComponent { - static propTypes = { - className: PropTypes.string, - query: PropTypes.object.isRequired, - isFavorite: PropTypes.bool, - organization: PropTypes.object, - property: PropTypes.string - }; - - static defaultProps = { - property: 'new_lines' - }; - - getFacetValueForOption(facet, option) { - const map = [ - '*-1000.0', - '1000.0-10000.0', - '10000.0-100000.0', - '100000.0-500000.0', - '500000.0-*' - ]; - return facet[map[option - 1]]; - } - - renderOption(option) { - return <span>{getSizeRatingLabel(option)}</span>; - } - - render() { - return ( - <FilterContainer - property={this.props.property} - className="leak-facet-box" - options={[1, 2, 3, 4, 5]} - query={this.props.query} - renderOption={this.renderOption} - isFavorite={this.props.isFavorite} - organization={this.props.organization} - getFacetValueForOption={this.getFacetValueForOption} - highlightUnder={1} - header={<FilterHeader name={translate('projects.facets.new_lines')} />} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx new file mode 100644 index 00000000000..29374492c73 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx @@ -0,0 +1,61 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; +import { translate } from '../../../helpers/l10n'; +import { getSizeRatingLabel } from '../../../helpers/ratings'; +import { Facet } from './Filter'; + +export interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; +} + +export default function NewLinesFilter(props: Props) { + const { property = 'new_lines' } = props; + + return ( + <FilterContainer + property={property} + className="leak-facet-box" + options={[1, 2, 3, 4, 5]} + query={props.query} + renderOption={renderOption} + isFavorite={props.isFavorite} + organization={props.organization} + getFacetValueForOption={getFacetValueForOption} + highlightUnder={1} + header={<FilterHeader name={translate('projects.facets.new_lines')} />} + /> + ); +} + +function getFacetValueForOption(facet: Facet, option: number) { + const map = ['*-1000.0', '1000.0-10000.0', '10000.0-100000.0', '100000.0-500000.0', '500000.0-*']; + return facet[map[option - 1]]; +} + +function renderOption(option: number) { + return <span>{getSizeRatingLabel(option)}</span>; +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx index b501476a25f..590645121b2 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx @@ -17,12 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon'; import IssuesFilter from './IssuesFilter'; import { translate } from '../../../helpers/l10n'; -export default function NewMaintainabilityFilter(props) { +interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} + +export default function NewMaintainabilityFilter(props: Props) { return ( <IssuesFilter {...props} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx index 9e42ab27e69..c2b6ff0e08d 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx @@ -17,12 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import BugIcon from '../../../components/icons-components/BugIcon'; import IssuesFilter from './IssuesFilter'; import { translate } from '../../../helpers/l10n'; -export default function NewReliabilityFilter(props) { +interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} + +export default function NewReliabilityFilter(props: Props) { return ( <IssuesFilter {...props} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx index 7d2e64e74c7..66d5bf07c5f 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx @@ -17,12 +17,19 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon'; import IssuesFilter from './IssuesFilter'; import { translate } from '../../../helpers/l10n'; -export default function NewSecurityFilter(props) { +interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} + +export default function NewSecurityFilter(props: Props) { return ( <IssuesFilter {...props} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx index 825ccb8625d..56ce79e0db6 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx @@ -17,40 +17,39 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; +import * as React from 'react'; import FilterContainer from './FilterContainer'; import FilterHeader from './FilterHeader'; import Level from '../../../components/ui/Level'; import { translate } from '../../../helpers/l10n'; +import { Facet } from './Filter'; -export default class QualityGateFilter extends React.PureComponent { - static propTypes = { - query: PropTypes.object.isRequired, - isFavorite: PropTypes.bool, - organization: PropTypes.object - }; +export interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} - getFacetValueForOption(facet, option) { - return facet[option]; - } +export default function QualityGateFilter(props: Props) { + return ( + <FilterContainer + property="gate" + options={['OK', 'WARN', 'ERROR']} + query={props.query} + renderOption={renderOption} + isFavorite={props.isFavorite} + organization={props.organization} + getFacetValueForOption={getFacetValueForOption} + header={<FilterHeader name={translate('projects.facets.quality_gate')} />} + /> + ); +} - renderOption(option, selected) { - return <Level level={option} small={true} muted={!selected} />; - } +function getFacetValueForOption(facet: Facet, option: string) { + return facet[option]; +} - render() { - return ( - <FilterContainer - property="gate" - options={['OK', 'WARN', 'ERROR']} - query={this.props.query} - renderOption={this.renderOption} - isFavorite={this.props.isFavorite} - organization={this.props.organization} - getFacetValueForOption={this.getFacetValueForOption} - header={<FilterHeader name={translate('projects.facets.quality_gate')} />} - /> - ); - } +function renderOption(option: string, selected: boolean) { + return <Level level={option} small={true} muted={!selected} />; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx index 07852afe335..99e3ce19bd0 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx @@ -17,9 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import IssuesFilter from './IssuesFilter'; -export default function ReliabilityFilter(props) { +interface Props { + className?: string; + headerDetail?: React.ReactNode; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} + +export default function ReliabilityFilter(props: Props) { return <IssuesFilter {...props} name="Reliability" property="reliability" />; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.tsx index 76cbdb067ca..5bc0764acdc 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.tsx @@ -17,50 +17,39 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -/*:: -type Props = { - className?: string, - handleSearch: (userString?: string) => void, - query: { search?: string } -}; -*/ - -/*:: -type State = { - userQuery?: string -}; -*/ +interface Props { + className?: string; + handleSearch: (userString?: string) => void; + query: { search?: string | undefined }; +} -export default class SearchFilter extends React.PureComponent { - /*:: props: Props; */ - /*:: state: State; */ +interface State { + userQuery?: string; +} - constructor(props /*: Props */) { +export default class SearchFilter extends React.PureComponent<Props, State> { + constructor(props: Props) { super(props); - this.state = { - userQuery: props.query.search - }; + this.state = { userQuery: props.query.search }; } - componentWillReceiveProps(nextProps /*: Props */) { + componentWillReceiveProps(nextProps: Props) { if ( this.props.query.search === this.state.userQuery && nextProps.query.search !== this.props.query.search ) { - this.setState({ - userQuery: nextProps.query.search || '' - }); + this.setState({ userQuery: nextProps.query.search || '' }); } } - handleQueryChange = ({ target } /*: { target: HTMLInputElement } */) => { - this.setState({ userQuery: target.value }); - if (!target.value || target.value.length >= 2) { - this.props.handleSearch(target.value); + handleQueryChange = (event: React.SyntheticEvent<HTMLInputElement>) => { + const { value } = event.currentTarget; + this.setState({ userQuery: value }); + if (!value || value.length >= 2) { + this.props.handleSearch(value); } }; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.js b/server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.tsx index 6e64dbf9bec..b17ca9a1a21 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.tsx @@ -17,36 +17,33 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; -import { withRouter } from 'react-router'; +import * as React from 'react'; +import * as PropTypes from 'prop-types'; import { debounce } from 'lodash'; import { getFilterUrl } from './utils'; import SearchFilter from './SearchFilter'; -/*:: -type Props = {| - className?: string, - query: { search?: string }, - router: { push: ({ pathname: string }) => void }, - isFavorite?: boolean, - organization?: {} -|}; -*/ +interface Props { + className?: string; + query: { search?: string }; + isFavorite?: boolean; + organization?: { key: string }; +} -class SearchFilterContainer extends React.PureComponent { - /*:: handleSearch: (userQuery?: string) => void; */ - /*:: props: Props; */ +export default class SearchFilterContainer extends React.PureComponent<Props> { + static contextTypes = { + router: PropTypes.object.isRequired + }; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); - this.handleSearch = debounce(this.handleSearch.bind(this), 250); + this.handleSearch = debounce(this.handleSearch, 250); } - handleSearch(userQuery /*: ?string */) { - const path = getFilterUrl(this.props, { search: userQuery || null }); - this.props.router.push(path); - } + handleSearch = (userQuery?: string) => { + const path = getFilterUrl(this.props, { search: userQuery }); + this.context.router.push(path); + }; render() { return ( @@ -58,5 +55,3 @@ class SearchFilterContainer extends React.PureComponent { ); } } - -export default withRouter(SearchFilterContainer); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.js b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.tsx index 25b7ff076d7..b81830abbb7 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.tsx @@ -17,48 +17,47 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; -import Select from 'react-select'; +import * as React from 'react'; +import * as Select from 'react-select'; +import * as PropTypes from 'prop-types'; import { getFilterUrl } from './utils'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - property: string, - query: {}, - options: Array<{ label: string, value: string }>, - router: { push: ({ pathname: string, query?: {} }) => void }, - onInputChange?: string => void, - onOpen?: void => void, - isLoading?: boolean, - isFavorite?: boolean, - organization?: {} -}; -*/ +interface Props { + property: string; + query: { [x: string]: any }; + options: Array<{ label: string; value: string }>; + onInputChange?: (query: string) => void; + onOpen?: () => void; + isLoading?: boolean; + isFavorite?: boolean; + organization?: { key: string }; +} -export default class SearchableFilterFooter extends React.PureComponent { - /*:: props: Props; */ +export default class SearchableFilterFooter extends React.PureComponent<Props> { + static contextTypes = { + router: PropTypes.object.isRequired + }; - handleOptionChange /*: ({ value: string }) => void */ = ({ value }) => { + handleOptionChange = ({ value }: { value: string }) => { const urlOptions = (this.props.query[this.props.property] || []).concat(value).join(','); const path = getFilterUrl(this.props, { [this.props.property]: urlOptions }); - this.props.router.push(path); + this.context.router.push(path); }; render() { return ( <div className="search-navigator-facet-footer projects-facet-footer"> <Select - onChange={this.handleOptionChange} className="input-super-large" - placeholder={translate('search_verb')} clearable={false} - searchable={true} + isLoading={this.props.isLoading} + onChange={this.handleOptionChange} onInputChange={this.props.onInputChange} onOpen={this.props.onOpen} - isLoading={this.props.isLoading} options={this.props.options} + placeholder={translate('search_verb')} + searchable={true} /> </div> ); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js deleted file mode 100644 index 16831ee169e..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import { translate } from '../../../helpers/l10n'; - -export default class SearchableFilterOption extends React.PureComponent { - static propTypes = { - optionKey: PropTypes.string.isRequired, - option: PropTypes.object - }; - - render() { - const optionName = this.props.option ? this.props.option.name : this.props.optionKey; - return <span>{this.props.optionKey !== '<null>' ? optionName : translate('unknown')}</span>; - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.js b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.tsx index 51b3512ca0e..06bf39a8289 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.tsx @@ -17,25 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; +import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - name: string, - children?: React.Element<*> -}; -*/ - -export default class FilterHeader extends React.PureComponent { - /*:: props: Props; */ +interface Props { + option?: { name: string }; + optionKey: string; +} - render() { - return ( - <div className="search-navigator-facet-header projects-facet-header"> - {this.props.name} - {this.props.children} - </div> - ); - } +export default function SearchableFilterOption(props: Props) { + const optionName = props.option ? props.option.name : props.optionKey; + return <span>{props.optionKey !== '<null>' ? optionName : translate('unknown')}</span>; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx index 9f7fa7aa997..27684ca9d93 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx @@ -17,9 +17,17 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import IssuesFilter from './IssuesFilter'; -export default function SecurityFilter(props) { +interface Props { + className?: string; + headerDetail?: React.ReactNode; + isFavorite?: boolean; + organization?: { key: string }; + query: { [x: string]: any }; +} + +export default function SecurityFilter(props: Props) { return <IssuesFilter {...props} name="Security" property="security" />; } diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js deleted file mode 100644 index 566f799ef8d..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import FilterContainer from './FilterContainer'; -import FilterHeader from './FilterHeader'; -import SizeRating from '../../../components/ui/SizeRating'; -import { translate } from '../../../helpers/l10n'; -import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings'; - -export default class SizeFilter extends React.PureComponent { - static propTypes = { - className: PropTypes.string, - query: PropTypes.object.isRequired, - isFavorite: PropTypes.bool, - organization: PropTypes.object, - property: PropTypes.string - }; - - static defaultProps = { - property: 'size' - }; - - getFacetValueForOption(facet, option) { - const map = [ - '*-1000.0', - '1000.0-10000.0', - '10000.0-100000.0', - '100000.0-500000.0', - '500000.0-*' - ]; - return facet[map[option - 1]]; - } - - renderOption(option, selected) { - return ( - <span> - <SizeRating value={getSizeRatingAverageValue(option)} small={true} muted={!selected} /> - <span className="spacer-left">{getSizeRatingLabel(option)}</span> - </span> - ); - } - - render() { - return ( - <FilterContainer - property={this.props.property} - className={this.props.className} - options={[1, 2, 3, 4, 5]} - query={this.props.query} - renderOption={this.renderOption} - isFavorite={this.props.isFavorite} - organization={this.props.organization} - getFacetValueForOption={this.getFacetValueForOption} - highlightUnder={1} - header={<FilterHeader name={translate('metric_domain.Size')} />} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx new file mode 100644 index 00000000000..36479e486b7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer'; +import FilterHeader from './FilterHeader'; +import SizeRating from '../../../components/ui/SizeRating'; +import { translate } from '../../../helpers/l10n'; +import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings'; +import { Facet } from './Filter'; + +export interface Props { + className?: string; + isFavorite?: boolean; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; +} + +export default function SizeFilter(props: Props) { + const { property = 'size' } = props; + + return ( + <FilterContainer + property={property} + className={props.className} + options={[1, 2, 3, 4, 5]} + query={props.query} + renderOption={renderOption} + isFavorite={props.isFavorite} + organization={props.organization} + getFacetValueForOption={getFacetValueForOption} + highlightUnder={1} + header={<FilterHeader name={translate('metric_domain.Size')} />} + /> + ); +} + +function getFacetValueForOption(facet: Facet, option: number) { + const map = ['*-1000.0', '1000.0-10000.0', '10000.0-100000.0', '100000.0-500000.0', '500000.0-*']; + return facet[map[option - 1]]; +} + +function renderOption(option: number, selected: boolean) { + return ( + <span> + <SizeRating value={getSizeRatingAverageValue(option)} small={true} muted={!selected} /> + <span className="spacer-left">{getSizeRatingLabel(option)}</span> + </span> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.js b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx index 22c5bfcb377..e163fed3b49 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx @@ -17,66 +17,63 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -//@flow -import React from 'react'; +import * as React from 'react'; import { debounce, difference, sortBy, size } from 'lodash'; -import Filter from './Filter'; +import Filter, { Facet } from './Filter'; import FilterHeader from './FilterHeader'; import SearchableFilterFooter from './SearchableFilterFooter'; import SearchableFilterOption from './SearchableFilterOption'; import { searchProjectTags } from '../../../api/components'; import { translate } from '../../../helpers/l10n'; -/*:: -type Props = { - query: {}, - router: { push: ({ pathname: string, query?: {} }) => void }, - value?: Array<string>, - facet?: {}, - isFavorite?: boolean, - organization?: {}, - maxFacetValue?: number -}; -*/ +interface Props { + facet?: Facet; + isFavorite?: boolean; + maxFacetValue?: number; + organization?: { key: string }; + property?: string; + query: { [x: string]: any }; + value?: string[]; +} -/*:: -type State = { - isLoading: boolean, - search: string, - tags: Array<string> -}; -*/ +interface State { + isLoading: boolean; + search: string; + tags: string[]; +} const LIST_SIZE = 10; -export default class TagsFilter extends React.PureComponent { - /*:: props: Props; */ - /*:: state: State; */ - /*:: property: string; */ +export default class TagsFilter extends React.PureComponent<Props, State> { + mounted: boolean; - constructor(props /*: Props */) { + constructor(props: Props) { super(props); this.state = { isLoading: false, search: '', tags: [] }; - this.property = 'tags'; - this.handleSearch = debounce(this.handleSearch.bind(this), 250); + this.handleSearch = debounce(this.handleSearch, 250); } - getSearchOptions( - facet /*: ?{} */, - tags /*: Array<string> */ - ) /*: Array<{ label: string, value: string }> */ { - let tagsCopy = [...tags]; - if (facet) { - tagsCopy = difference(tagsCopy, Object.keys(facet)); + componentDidMount() { + this.mounted = true; + } + + componentWillUnmount() { + this.mounted = false; + } + + getSearchOptions = () => { + let tagsCopy = [...this.state.tags]; + if (this.props.facet) { + tagsCopy = difference(tagsCopy, Object.keys(this.props.facet)); } return tagsCopy.slice(0, LIST_SIZE).map(tag => ({ label: tag, value: tag })); - } + }; - handleSearch = (search /*: ?string */) => { + handleSearch = (search?: string) => { if (search !== this.state.search) { search = search || ''; this.setState({ search, isLoading: true }); @@ -84,23 +81,26 @@ export default class TagsFilter extends React.PureComponent { q: search, ps: size(this.props.facet || {}) + LIST_SIZE }).then(result => { - this.setState({ isLoading: false, tags: result.tags }); + if (this.mounted) { + this.setState({ isLoading: false, tags: result.tags }); + } }); } }; - getSortedOptions(facet /*: {} */ = {}) { - return sortBy(Object.keys(facet), [option => -facet[option], option => option]); - } + getSortedOptions = (facet: Facet = {}) => + sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]); - getFacetValueForOption = (facet /*: {} */, option /*: string */) => facet[option]; + getFacetValueForOption = (facet: Facet = {}, option: string) => facet[option]; - renderOption = (option /*: string */) => <SearchableFilterOption optionKey={option} />; + renderOption = (option: string) => <SearchableFilterOption optionKey={option} />; render() { + const { property = 'tags' } = this.props; + return ( <Filter - property={this.property} + property={property} options={this.getSortedOptions(this.props.facet)} query={this.props.query} renderOption={this.renderOption} @@ -113,15 +113,14 @@ export default class TagsFilter extends React.PureComponent { header={<FilterHeader name={translate('projects.facets.tags')} />} footer={ <SearchableFilterFooter - property={this.property} - query={this.props.query} - options={this.getSearchOptions(this.props.facet, this.state.tags)} + isFavorite={this.props.isFavorite} isLoading={this.state.isLoading} - onOpen={this.handleSearch} onInputChange={this.handleSearch} - isFavorite={this.props.isFavorite} + onOpen={this.handleSearch} organization={this.props.organization} - router={this.props.router} + options={this.getSearchOptions()} + property={property} + query={this.props.query} /> } /> diff --git a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.js b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts index 8c841346a42..47d25834e5c 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts @@ -18,16 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { connect } from 'react-redux'; -import { withRouter } from 'react-router'; import TagsFilter from './TagsFilter'; import { getProjectsAppFacetByProperty, getProjectsAppMaxFacetValue } from '../../../store/rootReducer'; -const mapStateToProps = (state, ownProps) => ({ +const mapStateToProps = (state: any, ownProps: any) => ({ value: ownProps.query['tags'], facet: getProjectsAppFacetByProperty(state, 'tags'), maxFacetValue: getProjectsAppMaxFacetValue(state) }); -export default connect(mapStateToProps)(withRouter(TagsFilter)); +export default connect<any, any, any>(mapStateToProps)(TagsFilter); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx new file mode 100644 index 00000000000..29f2121767d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import CoverageFilter from '../CoverageFilter'; + +it('renders', () => { + const wrapper = shallow(<CoverageFilter query={{}} />); + expect(wrapper).toMatchSnapshot(); + + const renderOption = wrapper.prop('renderOption'); + expect(renderOption(2, false)).toMatchSnapshot(); + + const getFacetValueForOption = wrapper.prop('getFacetValueForOption'); + expect( + getFacetValueForOption( + { '80.0-*': 1, '70.0-80.0': 42, '50.0-70.0': 14, '30.0-50.0': 13, '*-30.0': 8, NO_DATA: 3 }, + 2 + ) + ).toBe(42); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/DuplicationsFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/DuplicationsFilter-test.tsx new file mode 100644 index 00000000000..2a4588325dc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/DuplicationsFilter-test.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import DuplicationsFilter from '../DuplicationsFilter'; + +it('renders', () => { + const wrapper = shallow(<DuplicationsFilter query={{}} />); + expect(wrapper).toMatchSnapshot(); + + const renderOption = wrapper.prop('renderOption'); + expect(renderOption(2, false)).toMatchSnapshot(); + expect(renderOption(6, true)).toMatchSnapshot(); + + const getFacetValueForOption = wrapper.prop('getFacetValueForOption'); + expect( + getFacetValueForOption( + { '*-3.0': 1, '3.0-5.0': 42, '5.0-10.0': 14, '10.0-20.0': 13, '20.0-*': 8, NO_DATA: 3 }, + 2 + ) + ).toBe(42); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx new file mode 100644 index 00000000000..51c9f5b2b52 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Filter from '../Filter'; + +it('renders', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('renders header and footer', () => { + expect(shallowRender({ header: <header />, footer: <footer /> })).toMatchSnapshot(); +}); + +it('renders no results', () => { + expect(shallowRender({ options: [] })).toMatchSnapshot(); +}); + +it('highlights under', () => { + expect(shallowRender({ highlightUnder: 1 })).toMatchSnapshot(); +}); + +it('renders selected', () => { + expect(shallowRender({ value: 2 })).toMatchSnapshot(); +}); + +it('hightlights under selected', () => { + expect(shallowRender({ highlightUnder: 1, value: 2 })).toMatchSnapshot(); +}); + +it('renders multiple selected', () => { + expect(shallowRender({ value: [1, 2] })).toMatchSnapshot(); +}); + +it('renders facet bar chart', () => { + expect( + shallowRender({ + getFacetValueForOption: (facet: any, option: any) => facet[option], + facet: { a: 17, b: 15, c: 24 }, + maxFacetValue: 24, + options: ['a', 'b', 'c'] + }) + ).toMatchSnapshot(); +}); + +function shallowRender(props?: any) { + return shallow( + <Filter + options={[1, 2, 3]} + property="foo" + query={{}} + renderOption={option => option} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/FilterHeader-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/FilterHeader-test.tsx new file mode 100644 index 00000000000..cb0301a76e5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/FilterHeader-test.tsx @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import FilterHeader from '../FilterHeader'; + +it('renders', () => { + expect(shallow(<FilterHeader name="foo" />)).toMatchSnapshot(); +}); + +it('renders with children', () => { + expect( + shallow( + <FilterHeader name="foo"> + <div /> + </FilterHeader> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx new file mode 100644 index 00000000000..6f839f171dc --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/IssuesFilter-test.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import IssuesFilter from '../IssuesFilter'; + +it('renders', () => { + const wrapper = shallow(<IssuesFilter name="bugs" query={{}} />); + expect(wrapper).toMatchSnapshot(); + + const renderOption = wrapper.prop('renderOption'); + expect(renderOption(2, false)).toMatchSnapshot(); + + const getFacetValueForOption = wrapper.prop('getFacetValueForOption'); + expect(getFacetValueForOption({ 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 }, 2)).toBe(2); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx index b5a84155f69..2669fd4f1ae 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/LanguagesFilter-test.tsx @@ -17,47 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import LanguagesFilter from '../LanguagesFilter'; const languages = { - java: { - key: 'java', - name: 'Java' - }, - cs: { - key: 'cs', - name: 'C#' - }, - js: { - key: 'js', - name: 'JavaScript' - }, - flex: { - key: 'flex', - name: 'Flex' - }, - php: { - key: 'php', - name: 'PHP' - }, - py: { - key: 'py', - name: 'Python' - } + java: { key: 'java', name: 'Java' }, + cs: { key: 'cs', name: 'C#' }, + js: { key: 'js', name: 'JavaScript' }, + flex: { key: 'flex', name: 'Flex' }, + php: { key: 'php', name: 'PHP' }, + py: { key: 'py', name: 'Python' } }; + const languagesFacet = { java: 39, cs: 4, js: 1 }; -const fakeRouter = { push: () => {} }; it('should render the languages without the ones in the facet', () => { const wrapper = shallow( - <LanguagesFilter - query={{ languages: null }} - languages={languages} - router={fakeRouter} - facet={languagesFacet} - /> + <LanguagesFilter query={{ languages: null }} languages={languages} facet={languagesFacet} /> ); expect(wrapper).toMatchSnapshot(); }); @@ -68,7 +45,6 @@ it('should render the languages facet with the selected languages', () => { query={{ languages: ['java', 'cs'] }} value={['java', 'cs']} languages={languages} - router={fakeRouter} facet={languagesFacet} isFavorite={true} /> @@ -94,7 +70,6 @@ it('should render maximum 10 languages in the searchbox results', () => { k: { key: 'k', name: 'k' }, l: { key: 'l', name: 'l' } }} - router={fakeRouter} facet={{ ...languagesFacet, g: 1 }} isFavorite={true} /> diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/MaintainabilityFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/MaintainabilityFilter-test.tsx new file mode 100644 index 00000000000..59add14532b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/MaintainabilityFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import MaintainabilityFilter from '../MaintainabilityFilter'; + +it('renders', () => { + expect(shallow(<MaintainabilityFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewCoverageFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewCoverageFilter-test.tsx new file mode 100644 index 00000000000..69f0855f75e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewCoverageFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NewCoverageFilter from '../NewCoverageFilter'; + +it('renders', () => { + expect(shallow(<NewCoverageFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewDuplicationsFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewDuplicationsFilter-test.tsx new file mode 100644 index 00000000000..9420d8257be --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewDuplicationsFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NewDuplicationsFilter from '../NewDuplicationsFilter'; + +it('renders', () => { + expect(shallow(<NewDuplicationsFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewLinesFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewLinesFilter-test.tsx new file mode 100644 index 00000000000..5760a50b122 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewLinesFilter-test.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NewLinesFilter from '../NewLinesFilter'; + +it('renders', () => { + const wrapper = shallow(<NewLinesFilter query={{}} />); + expect(wrapper).toMatchSnapshot(); + + const renderOption = wrapper.prop('renderOption'); + expect(renderOption(2, false)).toMatchSnapshot(); + + const getFacetValueForOption = wrapper.prop('getFacetValueForOption'); + expect( + getFacetValueForOption( + { + '*-1000.0': 1, + '1000.0-10000.0': 2, + '10000.0-100000.0': 3, + '100000.0-500000.0': 4, + '500000.0-*': 5 + }, + 2 + ) + ).toBe(2); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewMaintainabilityFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewMaintainabilityFilter-test.tsx new file mode 100644 index 00000000000..9dc78541994 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewMaintainabilityFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NewMaintainabilityFilter from '../NewMaintainabilityFilter'; + +it('renders', () => { + expect(shallow(<NewMaintainabilityFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewReliabilityFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewReliabilityFilter-test.tsx new file mode 100644 index 00000000000..2f97df9e72e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewReliabilityFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NewReliabilityFilter from '../NewReliabilityFilter'; + +it('renders', () => { + expect(shallow(<NewReliabilityFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewSecurityFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewSecurityFilter-test.tsx new file mode 100644 index 00000000000..a3771637e92 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/NewSecurityFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import NewSecurityFilter from '../NewSecurityFilter'; + +it('renders', () => { + expect(shallow(<NewSecurityFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx new file mode 100644 index 00000000000..02316f2426b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/QualityGateFilter-test.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import QualityGateFilter from '../QualityGateFilter'; + +it('renders', () => { + const wrapper = shallow(<QualityGateFilter query={{}} />); + expect(wrapper).toMatchSnapshot(); + + const renderOption = wrapper.prop('renderOption'); + expect(renderOption(2, false)).toMatchSnapshot(); + + const getFacetValueForOption = wrapper.prop('getFacetValueForOption'); + expect(getFacetValueForOption({ ERROR: 1, WARN: 2, OK: 3 }, 'WARN')).toBe(2); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/ReliabilityFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/ReliabilityFilter-test.tsx new file mode 100644 index 00000000000..8ca23ed4f50 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/ReliabilityFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import ReliabilityFilter from '../ReliabilityFilter'; + +it('renders', () => { + expect(shallow(<ReliabilityFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilter-test.tsx index 9e6594db965..2724f834a36 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilter-test.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilter-test.tsx @@ -17,23 +17,42 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import SearchFilter from '../SearchFilter'; +import { change } from '../../../../helpers/testUtils'; it('should render correctly without any search query', () => { - const wrapper = shallow(<SearchFilter handleSearch={() => {}} query={{ search: null }} />); + const wrapper = shallow(<SearchFilter handleSearch={jest.fn()} query={{}} />); expect(wrapper).toMatchSnapshot(); }); it('should render with a search query', () => { - const wrapper = shallow(<SearchFilter handleSearch={() => {}} query={{ search: 'foo' }} />); + const wrapper = shallow(<SearchFilter handleSearch={jest.fn()} query={{ search: 'foo' }} />); expect(wrapper).toMatchSnapshot(); }); it('should display a help message when there is less than 2 characters', () => { - const wrapper = shallow(<SearchFilter handleSearch={() => {}} query={{ search: 'a' }} />); + const wrapper = shallow(<SearchFilter handleSearch={jest.fn()} query={{ search: 'a' }} />); expect(wrapper).toMatchSnapshot(); wrapper.setState({ userQuery: 'foo' }); expect(wrapper).toMatchSnapshot(); }); + +it('searches', () => { + const handleSearch = jest.fn(); + const wrapper = shallow(<SearchFilter handleSearch={handleSearch} query={{}} />); + + change(wrapper.find('input'), 'a'); + expect(handleSearch).not.toBeCalled(); + + change(wrapper.find('input'), 'abc'); + expect(handleSearch).toBeCalledWith('abc'); +}); + +it('updates state to new props', () => { + const wrapper = shallow(<SearchFilter handleSearch={jest.fn()} query={{ search: 'abc' }} />); + expect(wrapper.state()).toEqual({ userQuery: 'abc' }); + wrapper.setProps({ query: { search: 'def' } }); + expect(wrapper.state()).toEqual({ userQuery: 'def' }); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx new file mode 100644 index 00000000000..7825caf1ca2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchFilterContainer-test.tsx @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import SearchFilterContainer from '../SearchFilterContainer'; + +// mocking lodash, because mocking timers is now working for some reason :'( +jest.mock('lodash', () => { + const lodash = require.requireActual('lodash'); + lodash.debounce = (fn: Function) => (...args: any[]) => fn(args); + return lodash; +}); + +it('searches', () => { + const push = jest.fn(); + const wrapper = shallow(<SearchFilterContainer query={{}} />, { context: { router: { push } } }); + expect(wrapper).toMatchSnapshot(); + wrapper.prop('handleSearch')('foo'); + expect(push).toBeCalledWith({ pathname: '/projects', query: { search: 'foo' } }); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx index c8f5a59ea74..2d12621ec21 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterFooter-test.tsx @@ -17,44 +17,41 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import SearchableFilterFooter from '../SearchableFilterFooter'; -const languageOptions = [ - { label: 'Flex', value: 'flex' }, - { label: 'PHP', value: 'php' }, - { label: 'Python', value: 'py' } -]; -const tagOptions = [ - { label: 'lang', value: 'lang' }, - { label: 'sonar', value: 'sonar' }, +const options = [ + { label: 'java', value: 'java' }, + { label: 'js', value: 'js' }, { label: 'csharp', value: 'csharp' } ]; -const fakeRouter = { push: () => {} }; -it('should render the languages without the ones in the facet', () => { +it('should render items without the ones in the facet', () => { const wrapper = shallow( <SearchableFilterFooter property="languages" - query={{ languages: null }} - options={languageOptions} - router={fakeRouter} - /> + query={{ languages: ['java'] }} + options={options} + />, + { context: { router: { push: jest.fn() } } } ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('Select').props().options.length).toBe(3); + expect(wrapper.find('Select').prop('options')).toMatchSnapshot(); }); -it('should render the tags without the ones in the facet', () => { +it('should render items without the ones in the facet', () => { + const push = jest.fn(); const wrapper = shallow( <SearchableFilterFooter - property="tags" - query={{ tags: ['java'] }} - options={tagOptions} - isFavorite={true} - /> + property="languages" + query={{ languages: ['java'] }} + options={options} + />, + { context: { router: { push } } } ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('Select').props().options.length).toBe(3); + (wrapper.find('Select').prop('onChange') as Function)({ value: 'js' }); + expect(push).toBeCalledWith({ + pathname: '/projects', + query: { languages: 'java,js' } + }); }); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterOption-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterOption-test.tsx new file mode 100644 index 00000000000..c5af6e76c6e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SearchableFilterOption-test.tsx @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import SearchableFilterOption from '../SearchableFilterOption'; + +it('renders', () => { + expect(shallow(<SearchableFilterOption optionKey="foo" />)).toMatchSnapshot(); + expect( + shallow(<SearchableFilterOption option={{ name: 'bar' }} optionKey="foo" />) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SecurityFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SecurityFilter-test.tsx new file mode 100644 index 00000000000..05c83d65c41 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SecurityFilter-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import SecurityFilter from '../SecurityFilter'; + +it('renders', () => { + expect(shallow(<SecurityFilter query={{}} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SizeFilter-test.tsx b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SizeFilter-test.tsx new file mode 100644 index 00000000000..486594f766d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/SizeFilter-test.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import SizeFilter from '../SizeFilter'; + +it('renders', () => { + const wrapper = shallow(<SizeFilter query={{}} />); + expect(wrapper).toMatchSnapshot(); + + const renderOption = wrapper.prop('renderOption'); + expect(renderOption(2, false)).toMatchSnapshot(); + + const getFacetValueForOption = wrapper.prop('getFacetValueForOption'); + expect( + getFacetValueForOption( + { + '*-1000.0': 1, + '1000.0-10000.0': 42, + '10000.0-100000.0': 14, + '100000.0-500000.0': 13, + '500000.0-*': 8 + }, + 2 + ) + ).toBe(42); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.js b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.tsx index ef6d8518c8d..bfc0790d00c 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/TagsFilter-test.tsx @@ -17,18 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import TagsFilter from '../TagsFilter'; const tags = ['lang', 'sonar', 'csharp', 'dotnet', 'it', 'net']; const tagsFacet = { lang: 4, sonar: 3, csharp: 1 }; -const fakeRouter = { push: () => {} }; it('should render the tags without the ones in the facet', () => { - const wrapper = shallow( - <TagsFilter query={{ tags: null }} router={fakeRouter} facet={tagsFacet} /> - ); + const wrapper = shallow(<TagsFilter query={{ tags: null }} facet={tagsFacet} />); expect(wrapper).toMatchSnapshot(); wrapper.setState({ tags }); expect(wrapper).toMatchSnapshot(); @@ -39,7 +36,6 @@ it('should render the tags facet with the selected tags', () => { <TagsFilter query={{ tags: ['lang', 'sonar'] }} value={['lang', 'sonar']} - router={fakeRouter} facet={tagsFacet} isFavorite={true} /> @@ -53,7 +49,6 @@ it('should render maximum 10 tags in the searchbox results', () => { <TagsFilter query={{ languages: ['java', 'ad'] }} value={['java', 'ad']} - router={fakeRouter} facet={{ ...tagsFacet, ad: 1 }} isFavorite={true} /> diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap new file mode 100644 index 00000000000..d475b329ae7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/CoverageFilter-test.tsx.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Connect(Filter) + getFacetValueForOption={[Function]} + header={ + <FilterHeader + name="metric_domain.Coverage" + /> + } + highlightUnder={1} + highlightUnderMax={5} + options={ + Array [ + 1, + 2, + 3, + 4, + 5, + 6, + ] + } + property="coverage" + query={Object {}} + renderOption={[Function]} +/> +`; + +exports[`renders 2`] = ` +<span> + <CoverageRating + muted={true} + size="small" + value={75} + /> + <span + className="spacer-left" + > + < 80% + </span> +</span> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap new file mode 100644 index 00000000000..ff87f83526b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/DuplicationsFilter-test.tsx.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Connect(Filter) + getFacetValueForOption={[Function]} + header={ + <FilterHeader + name="metric_domain.Duplications" + /> + } + highlightUnder={1} + highlightUnderMax={5} + options={ + Array [ + 1, + 2, + 3, + 4, + 5, + 6, + ] + } + property="duplications" + query={Object {}} + renderOption={[Function]} +/> +`; + +exports[`renders 2`] = ` +<span> + <DuplicationsRating + muted={true} + size="small" + value={4} + /> + <span + className="spacer-left" + > + ≥ 3% + </span> +</span> +`; + +exports[`renders 3`] = ` +<span> + <span + className="spacer-left" + > + <span + className="big-spacer-left" + > + no_data + </span> + </span> +</span> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/Filter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/Filter-test.tsx.snap new file mode 100644 index 00000000000..684a65f0ce8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/Filter-test.tsx.snap @@ -0,0 +1,596 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`highlights under 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key={1} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 1, + }, + } + } + > + <span + className="facet-name" + > + 1 + </span> + </Link> + <div + className="search-navigator-facet-highlight-under-container" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key={2} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 2, + }, + } + } + > + <span + className="facet-name" + > + 2 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={3} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 3, + }, + } + } + > + <span + className="facet-name" + > + 3 + </span> + </Link> + </div> + </div> +</div> +`; + +exports[`hightlights under selected 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key={1} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 1, + }, + } + } + > + <span + className="facet-name" + > + 1 + </span> + </Link> + <div + className="search-navigator-facet-highlight-under-container" + > + <Link + className="facet search-navigator-facet projects-facet active" + data-key={2} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object {}, + } + } + > + <span + className="facet-name" + > + 2 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={3} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 3, + }, + } + } + > + <span + className="facet-name" + > + 3 + </span> + </Link> + </div> + </div> +</div> +`; + +exports[`renders 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key={1} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 1, + }, + } + } + > + <span + className="facet-name" + > + 1 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={2} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 2, + }, + } + } + > + <span + className="facet-name" + > + 2 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={3} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 3, + }, + } + } + > + <span + className="facet-name" + > + 3 + </span> + </Link> + </div> +</div> +`; + +exports[`renders facet bar chart 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key="a" + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": "a", + }, + } + } + > + <span + className="facet-name" + > + a + </span> + <span + className="facet-stat" + > + 17 + <div + className="projects-facet-bar" + > + <div + className="projects-facet-bar-inner" + style={ + Object { + "width": 42.5, + } + } + /> + </div> + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key="b" + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": "b", + }, + } + } + > + <span + className="facet-name" + > + b + </span> + <span + className="facet-stat" + > + 15 + <div + className="projects-facet-bar" + > + <div + className="projects-facet-bar-inner" + style={ + Object { + "width": 37.5, + } + } + /> + </div> + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key="c" + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": "c", + }, + } + } + > + <span + className="facet-name" + > + c + </span> + <span + className="facet-stat" + > + 24 + <div + className="projects-facet-bar" + > + <div + className="projects-facet-bar-inner" + style={ + Object { + "width": 60, + } + } + /> + </div> + </span> + </Link> + </div> +</div> +`; + +exports[`renders header and footer 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <header /> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key={1} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 1, + }, + } + } + > + <span + className="facet-name" + > + 1 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={2} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 2, + }, + } + } + > + <span + className="facet-name" + > + 2 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={3} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 3, + }, + } + } + > + <span + className="facet-name" + > + 3 + </span> + </Link> + </div> + <footer /> +</div> +`; + +exports[`renders multiple selected 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet active" + data-key={1} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": "2", + }, + } + } + > + <span + className="facet-name" + > + 1 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet active" + data-key={2} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": "1", + }, + } + } + > + <span + className="facet-name" + > + 2 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={3} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": "1,2,3", + }, + } + } + > + <span + className="facet-name" + > + 3 + </span> + </Link> + </div> +</div> +`; + +exports[`renders no results 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-empty" + > + no_results + </div> +</div> +`; + +exports[`renders selected 1`] = ` +<div + className="search-navigator-facet-box" + data-key="foo" +> + <div + className="search-navigator-facet-list projects-facet-list" + > + <Link + className="facet search-navigator-facet projects-facet" + data-key={1} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 1, + }, + } + } + > + <span + className="facet-name" + > + 1 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet active" + data-key={2} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object {}, + } + } + > + <span + className="facet-name" + > + 2 + </span> + </Link> + <Link + className="facet search-navigator-facet projects-facet" + data-key={3} + onClick={[Function]} + onlyActiveOnIndex={false} + style={Object {}} + to={ + Object { + "pathname": "/projects", + "query": Object { + "foo": 3, + }, + } + } + > + <span + className="facet-name" + > + 3 + </span> + </Link> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/FilterHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/FilterHeader-test.tsx.snap new file mode 100644 index 00000000000..b1f4611eae9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/FilterHeader-test.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="search-navigator-facet-header projects-facet-header" +> + foo +</div> +`; + +exports[`renders with children 1`] = ` +<div + className="search-navigator-facet-header projects-facet-header" +> + foo + <div /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap new file mode 100644 index 00000000000..2177f4c16e9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/IssuesFilter-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Connect(Filter) + getFacetValueForOption={[Function]} + header={ + <FilterHeader + name="metric_domain.bugs" + /> + } + highlightUnder={1} + options={ + Array [ + 1, + 2, + 3, + 4, + 5, + ] + } + query={Object {}} + renderOption={[Function]} +/> +`; + +exports[`renders 2`] = ` +<span> + <Rating + muted={true} + small={true} + value={2} + /> + <span + className="note spacer-left" + > + and_worse + </span> +</span> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.js.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.tsx.snap index 78b7575ab4e..38b72391839 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.tsx.snap @@ -66,15 +66,9 @@ exports[`should render maximum 10 languages in the searchbox results 1`] = ` ], } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.languages" @@ -145,15 +139,9 @@ exports[`should render the languages facet with the selected languages 1`] = ` ], } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.languages" @@ -327,11 +315,6 @@ exports[`should render the languages facet with the selected languages 2`] = ` ], } } - router={ - Object { - "push": [Function], - } - } /> </div> `; @@ -369,15 +352,9 @@ exports[`should render the languages without the ones in the facet 1`] = ` "languages": null, } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.languages" diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/MaintainabilityFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/MaintainabilityFilter-test.tsx.snap new file mode 100644 index 00000000000..ef3d9057b14 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/MaintainabilityFilter-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<IssuesFilter + name="Maintainability" + property="maintainability" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewCoverageFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewCoverageFilter-test.tsx.snap new file mode 100644 index 00000000000..08f6a0bcf1f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewCoverageFilter-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<CoverageFilter + className="leak-facet-box" + property="new_coverage" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewDuplicationsFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewDuplicationsFilter-test.tsx.snap new file mode 100644 index 00000000000..a75df7d187c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewDuplicationsFilter-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<DuplicationsFilter + className="leak-facet-box" + property="new_duplications" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap new file mode 100644 index 00000000000..4fde9336313 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewLinesFilter-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Connect(Filter) + className="leak-facet-box" + getFacetValueForOption={[Function]} + header={ + <FilterHeader + name="projects.facets.new_lines" + /> + } + highlightUnder={1} + options={ + Array [ + 1, + 2, + 3, + 4, + 5, + ] + } + property="new_lines" + query={Object {}} + renderOption={[Function]} +/> +`; + +exports[`renders 2`] = ` +<span> + ≥ 1k +</span> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewMaintainabilityFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewMaintainabilityFilter-test.tsx.snap new file mode 100644 index 00000000000..dc9057aa022 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewMaintainabilityFilter-test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<IssuesFilter + className="leak-facet-box" + headerDetail={ + <span + className="note little-spacer-left" + > + ( + <CodeSmellIcon + className="little-spacer-right" + /> + metric.code_smells.name + ) + </span> + } + name="Maintainability" + property="new_maintainability" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewReliabilityFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewReliabilityFilter-test.tsx.snap new file mode 100644 index 00000000000..b93c2e876b5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewReliabilityFilter-test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<IssuesFilter + className="leak-facet-box" + headerDetail={ + <span + className="note little-spacer-left" + > + ( + <BugIcon + className="little-spacer-right" + /> + metric.bugs.name + ) + </span> + } + name="Reliability" + property="new_reliability" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewSecurityFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewSecurityFilter-test.tsx.snap new file mode 100644 index 00000000000..f0005022a9d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/NewSecurityFilter-test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<IssuesFilter + className="leak-facet-box" + headerDetail={ + <span + className="note little-spacer-left" + > + ( + <VulnerabilityIcon + className="little-spacer-right" + /> + metric.vulnerabilities.name + ) + </span> + } + name="Security" + property="new_security" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap new file mode 100644 index 00000000000..c20cd46404a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/QualityGateFilter-test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Connect(Filter) + getFacetValueForOption={[Function]} + header={ + <FilterHeader + name="projects.facets.quality_gate" + /> + } + options={ + Array [ + "OK", + "WARN", + "ERROR", + ] + } + property="gate" + query={Object {}} + renderOption={[Function]} +/> +`; + +exports[`renders 2`] = ` +<Level + level={2} + muted={true} + small={true} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/ReliabilityFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/ReliabilityFilter-test.tsx.snap new file mode 100644 index 00000000000..4494914b160 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/ReliabilityFilter-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<IssuesFilter + name="Reliability" + property="reliability" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilter-test.js.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilter-test.tsx.snap index 86e0c761aa1..86e0c761aa1 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilter-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilter-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilterContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilterContainer-test.tsx.snap new file mode 100644 index 00000000000..d3d2ac3eb74 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchFilterContainer-test.tsx.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`searches 1`] = ` +<SearchFilter + handleSearch={[Function]} + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.js.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.js.snap deleted file mode 100644 index b0346a32181..00000000000 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.js.snap +++ /dev/null @@ -1,131 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render the languages without the ones in the facet 1`] = ` -<div - className="search-navigator-facet-footer projects-facet-footer" -> - <Select - addLabelText="Add \\"{label}\\"?" - arrowRenderer={[Function]} - autosize={true} - backspaceRemoves={true} - backspaceToRemoveMessage="Press backspace to remove {label}" - className="input-super-large" - clearAllText="Clear all" - clearRenderer={[Function]} - clearValueText="Clear value" - clearable={false} - deleteRemoves={true} - delimiter="," - disabled={false} - escapeClearsValue={true} - filterOptions={[Function]} - ignoreAccents={true} - ignoreCase={true} - inputProps={Object {}} - isLoading={false} - joinValues={false} - labelKey="label" - matchPos="any" - matchProp="any" - menuBuffer={0} - menuRenderer={[Function]} - multi={false} - noResultsText="No results found" - onBlurResetsInput={true} - onChange={[Function]} - onCloseResetsInput={true} - optionComponent={[Function]} - options={ - Array [ - Object { - "label": "Flex", - "value": "flex", - }, - Object { - "label": "PHP", - "value": "php", - }, - Object { - "label": "Python", - "value": "py", - }, - ] - } - pageSize={5} - placeholder="search_verb" - required={false} - scrollMenuIntoView={true} - searchable={true} - simpleValue={false} - tabSelectsValue={true} - valueComponent={[Function]} - valueKey="value" - /> -</div> -`; - -exports[`should render the tags without the ones in the facet 1`] = ` -<div - className="search-navigator-facet-footer projects-facet-footer" -> - <Select - addLabelText="Add \\"{label}\\"?" - arrowRenderer={[Function]} - autosize={true} - backspaceRemoves={true} - backspaceToRemoveMessage="Press backspace to remove {label}" - className="input-super-large" - clearAllText="Clear all" - clearRenderer={[Function]} - clearValueText="Clear value" - clearable={false} - deleteRemoves={true} - delimiter="," - disabled={false} - escapeClearsValue={true} - filterOptions={[Function]} - ignoreAccents={true} - ignoreCase={true} - inputProps={Object {}} - isLoading={false} - joinValues={false} - labelKey="label" - matchPos="any" - matchProp="any" - menuBuffer={0} - menuRenderer={[Function]} - multi={false} - noResultsText="No results found" - onBlurResetsInput={true} - onChange={[Function]} - onCloseResetsInput={true} - optionComponent={[Function]} - options={ - Array [ - Object { - "label": "lang", - "value": "lang", - }, - Object { - "label": "sonar", - "value": "sonar", - }, - Object { - "label": "csharp", - "value": "csharp", - }, - ] - } - pageSize={5} - placeholder="search_verb" - required={false} - scrollMenuIntoView={true} - searchable={true} - simpleValue={false} - tabSelectsValue={true} - valueComponent={[Function]} - valueKey="value" - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.tsx.snap new file mode 100644 index 00000000000..6cf27012dfd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterFooter-test.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render items without the ones in the facet 1`] = ` +Array [ + Object { + "label": "java", + "value": "java", + }, + Object { + "label": "js", + "value": "js", + }, + Object { + "label": "csharp", + "value": "csharp", + }, +] +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterOption-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterOption-test.tsx.snap new file mode 100644 index 00000000000..43960a58f7e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SearchableFilterOption-test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<span> + foo +</span> +`; + +exports[`renders 2`] = ` +<span> + bar +</span> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityFilter-test.tsx.snap new file mode 100644 index 00000000000..7142354de29 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SecurityFilter-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<IssuesFilter + name="Security" + property="security" + query={Object {}} +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap new file mode 100644 index 00000000000..e4ec3a9aa79 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/SizeFilter-test.tsx.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Connect(Filter) + getFacetValueForOption={[Function]} + header={ + <FilterHeader + name="metric_domain.Size" + /> + } + highlightUnder={1} + options={ + Array [ + 1, + 2, + 3, + 4, + 5, + ] + } + property="size" + query={Object {}} + renderOption={[Function]} +/> +`; + +exports[`renders 2`] = ` +<span> + <SizeRating + muted={true} + small={true} + value={5000} + /> + <span + className="spacer-left" + > + ≥ 1k + </span> +</span> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.js.snap b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.tsx.snap index c184c5e901a..22f13a9b832 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.js.snap +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.tsx.snap @@ -69,15 +69,9 @@ exports[`should render maximum 10 tags in the searchbox results 1`] = ` ], } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.tags" @@ -136,15 +130,9 @@ exports[`should render the tags facet with the selected tags 1`] = ` ], } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.tags" @@ -288,11 +276,6 @@ exports[`should render the tags facet with the selected tags 2`] = ` ], } } - router={ - Object { - "push": [Function], - } - } /> </div> `; @@ -318,15 +301,9 @@ exports[`should render the tags without the ones in the facet 1`] = ` "tags": null, } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.tags" @@ -385,15 +362,9 @@ exports[`should render the tags without the ones in the facet 2`] = ` "tags": null, } } - router={ - Object { - "push": [Function], - } - } /> } getFacetValueForOption={[Function]} - halfWidth={false} header={ <FilterHeader name="projects.facets.tags" diff --git a/server/sonar-web/src/main/js/apps/projects/filters/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/utils-test.ts new file mode 100644 index 00000000000..a888c165cc0 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/filters/__tests__/utils-test.ts @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { getFilterUrl } from '../utils'; + +it('works in trivial cases', () => { + expect(getFilterUrl({ query: {} }, {})).toEqual({ pathname: '/projects', query: {} }); + expect(getFilterUrl({ query: { foo: 'bar' } }, {})).toEqual({ + pathname: '/projects', + query: { foo: 'bar' } + }); + expect(getFilterUrl({ query: {} }, { foo: 'bar' })).toEqual({ + pathname: '/projects', + query: { foo: 'bar' } + }); + expect(getFilterUrl({ query: { foo: 'bar' } }, { foo: 'qux' })).toEqual({ + pathname: '/projects', + query: { foo: 'qux' } + }); + expect(getFilterUrl({ query: { foo: 'bar' } }, { baz: 'qux' })).toEqual({ + pathname: '/projects', + query: { foo: 'bar', baz: 'qux' } + }); +}); + +it('works for favorites', () => { + expect(getFilterUrl({ isFavorite: true, query: {} }, { foo: 'bar' })).toEqual({ + pathname: '/projects/favorite', + query: { foo: 'bar' } + }); +}); + +it('works with organization', () => { + expect(getFilterUrl({ organization: { key: 'org' }, query: {} }, { foo: 'bar' })).toEqual({ + pathname: '/organizations/org/projects', + query: { foo: 'bar' } + }); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/filters/utils.js b/server/sonar-web/src/main/js/apps/projects/filters/utils.ts index 5be12186542..18887a3cc38 100644 --- a/server/sonar-web/src/main/js/apps/projects/filters/utils.js +++ b/server/sonar-web/src/main/js/apps/projects/filters/utils.ts @@ -18,17 +18,24 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { each, isNil, omitBy } from 'lodash'; +import { RawQuery } from '../../../helpers/query'; -export const getFilterUrl = (ownProps, part) => { +interface OwnProps { + isFavorite?: boolean; + organization?: { key: string }; + query: RawQuery; +} + +export function getFilterUrl(ownProps: OwnProps, part: RawQuery) { const basePathName = ownProps.organization ? `/organizations/${ownProps.organization.key}/projects` : '/projects'; const pathname = basePathName + (ownProps.isFavorite ? '/favorite' : ''); - const query = omitBy({ ...ownProps.query, ...part }, isNil); + const query: RawQuery = omitBy({ ...ownProps.query, ...part }, isNil); each(query, (value, key) => { if (Array.isArray(value)) { query[key] = value.join(','); } }); return { pathname, query }; -}; +} diff --git a/server/sonar-web/src/main/js/apps/projects/types.ts b/server/sonar-web/src/main/js/apps/projects/types.ts new file mode 100644 index 00000000000..a78e422460f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/types.ts @@ -0,0 +1,25 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +export interface Project { + key: string; + measures: { [key: string]: string }; + name: string; + organization?: { name: string }; +} diff --git a/server/sonar-web/src/main/js/apps/projects/utils.js b/server/sonar-web/src/main/js/apps/projects/utils.ts index b322061f662..6a03e7b57cf 100644 --- a/server/sonar-web/src/main/js/apps/projects/utils.js +++ b/server/sonar-web/src/main/js/apps/projects/utils.ts @@ -17,10 +17,14 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow import { translate } from '../../helpers/l10n'; -export const SORTING_METRICS = [ +interface SortingOption { + class?: string; + value: string; +} + +export const SORTING_METRICS: SortingOption[] = [ { value: 'name' }, { value: 'analysis_date' }, { value: 'reliability' }, @@ -31,7 +35,7 @@ export const SORTING_METRICS = [ { value: 'size' } ]; -export const SORTING_LEAK_METRICS = [ +export const SORTING_LEAK_METRICS: SortingOption[] = [ { value: 'name' }, { value: 'analysis_date' }, { value: 'new_reliability', class: 'projects-leak-sorting-option' }, @@ -42,7 +46,7 @@ export const SORTING_LEAK_METRICS = [ { value: 'new_lines', class: 'projects-leak-sorting-option' } ]; -export const SORTING_SWITCH = { +export const SORTING_SWITCH: { [x: string]: string } = { analysis_date: 'analysis_date', name: 'name', reliability: 'new_reliability', @@ -70,11 +74,11 @@ export const VISUALIZATIONS = [ 'duplications' ]; -export const localizeSorting = (sort /*: ?string */) => { +export function localizeSorting(sort?: string): string { return translate('projects.sort', sort || 'name'); -}; +} -export function parseSorting(sort /*: string */) /*: { sortValue: string, sortDesc: boolean } */ { +export function parseSorting(sort: string): { sortValue: string; sortDesc: boolean } { const desc = sort[0] === '-'; return { sortValue: desc ? sort.substr(1) : sort, sortDesc: desc }; } diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Duplications.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Coverage.tsx index 5df77506a7a..1feed7f3c75 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Duplications.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Coverage.tsx @@ -17,19 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import SimpleBubbleChart from './SimpleBubbleChart'; +import { Project } from '../types'; -export default class Duplications extends React.PureComponent { - render() { - return ( - <SimpleBubbleChart - {...this.props} - xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} - yMetric={{ key: 'duplicated_lines', type: 'SHORT_INT' }} - sizeMetric={{ key: 'duplicated_blocks', type: 'SHORT_INT' }} - /> - ); - } +interface Props { + displayOrganizations: boolean; + projects: Project[]; +} + +export default function Coverage(props: Props) { + return ( + <SimpleBubbleChart + {...props} + xMetric={{ key: 'complexity', type: 'SHORT_INT' }} + yMetric={{ key: 'coverage', type: 'PERCENT' }} + yDomain={[100, 0]} + sizeMetric={{ key: 'uncovered_lines', type: 'SHORT_INT' }} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Coverage.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Duplications.tsx index 66ad04d16a2..2e6c05b0dc9 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Coverage.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Duplications.tsx @@ -17,20 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import SimpleBubbleChart from './SimpleBubbleChart'; +import { Project } from '../types'; -export default class Coverage extends React.PureComponent { - render() { - return ( - <SimpleBubbleChart - {...this.props} - xMetric={{ key: 'complexity', type: 'SHORT_INT' }} - yMetric={{ key: 'coverage', type: 'PERCENT' }} - yDomain={[100, 0]} - sizeMetric={{ key: 'uncovered_lines', type: 'SHORT_INT' }} - /> - ); - } +interface Props { + displayOrganizations: boolean; + projects: Project[]; +} + +export default function Duplications(props: Props) { + return ( + <SimpleBubbleChart + {...props} + xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} + yMetric={{ key: 'duplicated_lines', type: 'SHORT_INT' }} + sizeMetric={{ key: 'duplicated_blocks', type: 'SHORT_INT' }} + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Maintainability.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Maintainability.tsx index 886e22f2f18..9d137efb5a4 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Maintainability.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Maintainability.tsx @@ -17,20 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import SimpleBubbleChart from './SimpleBubbleChart'; +import { Project } from '../types'; -export default class Maintainability extends React.PureComponent { - render() { - return ( - <SimpleBubbleChart - {...this.props} - xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} - yMetric={{ key: 'sqale_index', type: 'SHORT_WORK_DUR' }} - sizeMetric={{ key: 'code_smells', type: 'SHORT_INT' }} - colorMetric="sqale_rating" - /> - ); - } +interface Props { + displayOrganizations: boolean; + projects: Project[]; +} + +export default function Maintainability(props: Props) { + return ( + <SimpleBubbleChart + {...props} + xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} + yMetric={{ key: 'sqale_index', type: 'SHORT_WORK_DUR' }} + sizeMetric={{ key: 'code_smells', type: 'SHORT_INT' }} + colorMetric="sqale_rating" + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Reliability.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Reliability.tsx index 18af93db288..15778724935 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Reliability.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Reliability.tsx @@ -17,20 +17,23 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import SimpleBubbleChart from './SimpleBubbleChart'; +import { Project } from '../types'; -export default class Reliability extends React.PureComponent { - render() { - return ( - <SimpleBubbleChart - {...this.props} - xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} - yMetric={{ key: 'reliability_remediation_effort', type: 'SHORT_WORK_DUR' }} - sizeMetric={{ key: 'bugs', type: 'SHORT_INT' }} - colorMetric="reliability_rating" - /> - ); - } +interface Props { + displayOrganizations: boolean; + projects: Project[]; +} + +export default function Reliability(props: Props) { + return ( + <SimpleBubbleChart + {...props} + xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} + yMetric={{ key: 'reliability_remediation_effort', type: 'SHORT_WORK_DUR' }} + sizeMetric={{ key: 'bugs', type: 'SHORT_INT' }} + colorMetric="reliability_rating" + /> + ); } diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.tsx index 880b07fa10a..8f7d1aedbbc 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Risk.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; +import { Project } from '../types'; import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend'; import BubbleChart from '../../../components/charts/BubbleChart'; import { formatMeasure } from '../../../helpers/measures'; @@ -26,15 +26,6 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { RATING_COLORS } from '../../../helpers/constants'; import { getProjectUrl } from '../../../helpers/urls'; -/*:: -type Project = { - key: string, - measures: { [string]: string }, - name: string, - organization?: { name: string } -}; -*/ - const X_METRIC = 'sqale_index'; const X_METRIC_TYPE = 'SHORT_WORK_DUR'; const Y_METRIC = 'coverage'; @@ -45,26 +36,25 @@ const COLOR_METRIC_1 = 'reliability_rating'; const COLOR_METRIC_2 = 'security_rating'; const COLOR_METRIC_TYPE = 'RATING'; -export default class Risk extends React.PureComponent { - /*:: props: { - displayOrganizations: boolean, - projects: Array<Project> - }; -*/ +interface Props { + displayOrganizations: boolean; + projects: Project[]; +} - getMetricTooltip(metric /*: { key: string, type: string } */, value /*: ?number */) { +export default class Risk extends React.PureComponent<Props> { + getMetricTooltip(metric: { key: string; type: string }, value?: number) { const name = translate('metric', metric.key, 'name'); const formattedValue = value != null ? formatMeasure(value, metric.type) : '–'; return `<div>${name}: ${formattedValue}</div>`; } getTooltip( - project /*: Project */, - x /*: ?number */, - y /*: ?number */, - size /*: ?number */, - color1 /*: ?number */, - color2 /*: ?number */ + project: Project, + x?: number, + y?: number, + size?: number, + color1?: number, + color2?: number ) { const fullProjectName = this.props.displayOrganizations && project.organization @@ -83,14 +73,18 @@ export default class Risk extends React.PureComponent { render() { const items = this.props.projects.map(project => { - const x = project.measures[X_METRIC] != null ? Number(project.measures[X_METRIC]) : null; - const y = project.measures[Y_METRIC] != null ? Number(project.measures[Y_METRIC]) : null; + const x = project.measures[X_METRIC] != null ? Number(project.measures[X_METRIC]) : undefined; + const y = project.measures[Y_METRIC] != null ? Number(project.measures[Y_METRIC]) : undefined; const size = - project.measures[SIZE_METRIC] != null ? Number(project.measures[SIZE_METRIC]) : null; + project.measures[SIZE_METRIC] != null ? Number(project.measures[SIZE_METRIC]) : undefined; const color1 = - project.measures[COLOR_METRIC_1] != null ? Number(project.measures[COLOR_METRIC_1]) : null; + project.measures[COLOR_METRIC_1] != null + ? Number(project.measures[COLOR_METRIC_1]) + : undefined; const color2 = - project.measures[COLOR_METRIC_2] != null ? Number(project.measures[COLOR_METRIC_2]) : null; + project.measures[COLOR_METRIC_2] != null + ? Number(project.measures[COLOR_METRIC_2]) + : undefined; return { x: x || 0, y: y || 0, @@ -104,8 +98,10 @@ export default class Risk extends React.PureComponent { link: getProjectUrl(project.key) }; }); - const formatXTick = tick => formatMeasure(tick, X_METRIC_TYPE); - const formatYTick = tick => formatMeasure(tick, Y_METRIC_TYPE); + + const formatXTick = (tick: number) => formatMeasure(tick, X_METRIC_TYPE); + const formatYTick = (tick: number) => formatMeasure(tick, Y_METRIC_TYPE); + return ( <div> <BubbleChart diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Security.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/Security.tsx new file mode 100644 index 00000000000..ca328013284 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Security.tsx @@ -0,0 +1,39 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 SimpleBubbleChart from './SimpleBubbleChart'; +import { Project } from '../types'; + +interface Props { + displayOrganizations: boolean; + projects: Project[]; +} + +export default function Security(props: Props) { + return ( + <SimpleBubbleChart + {...props} + xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} + yMetric={{ key: 'security_remediation_effort', type: 'SHORT_WORK_DUR' }} + sizeMetric={{ key: 'vulnerabilities', type: 'SHORT_INT' }} + colorMetric="security_rating" + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js b/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.tsx index 88ca5cafbef..0b3a866353e 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/SimpleBubbleChart.tsx @@ -17,53 +17,38 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import ColorRatingsLegend from '../../../components/charts/ColorRatingsLegend'; import BubbleChart from '../../../components/charts/BubbleChart'; import { formatMeasure } from '../../../helpers/measures'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { RATING_COLORS } from '../../../helpers/constants'; import { getProjectUrl } from '../../../helpers/urls'; +import { Project } from '../types'; -/*:: -type Metric = { key: string, type: string }; -*/ - -/*:: -type Project = { - key: string, - measures: { [string]: string }, - name: string, - organization?: { name: string } -}; -*/ +export interface Metric { + key: string; + type: string; +} -export default class SimpleBubbleChart extends React.PureComponent { - /*:: props: { - displayOrganizations: boolean, - projects: Array<Project>, - sizeMetric: Metric, - xMetric: Metric, - yDomain?: [number, number], - yMetric: Metric, - colorMetric?: string - }; -*/ +interface Props { + colorMetric?: string; + displayOrganizations: boolean; + projects: Project[]; + sizeMetric: Metric; + xMetric: Metric; + yDomain?: [number, number]; + yMetric: Metric; +} - getMetricTooltip(metric /*: Metric */, value /*: ?number */) { +export default class SimpleBubbleChart extends React.PureComponent<Props> { + getMetricTooltip(metric: Metric, value?: number) { const name = translate('metric', metric.key, 'name'); const formattedValue = value != null ? formatMeasure(value, metric.type) : '–'; return `<div>${name}: ${formattedValue}</div>`; } - getTooltip( - project /*: Project */, - x /*: ?number */, - y /*: ?number */, - size /*: ?number */, - color /*: ?number */ - ) { + getTooltip(project: Project, x?: number, y?: number, size?: number, color?: number) { const fullProjectName = this.props.displayOrganizations && project.organization ? `${project.organization.name} / <strong>${project.name}</strong>` @@ -77,8 +62,8 @@ export default class SimpleBubbleChart extends React.PureComponent { ]; if (color) { - // $FlowFixMe if `color` is defined then `this.props.colorMetric` is defined too - this.getMetricTooltip({ key: this.props.colorMetric, type: 'RATING' }, color); + // if `color` is defined then `this.props.colorMetric` is defined too + this.getMetricTooltip({ key: this.props.colorMetric!, type: 'RATING' }, color); } return `<div class="text-left">${inner.join('')}</div>`; @@ -91,13 +76,13 @@ export default class SimpleBubbleChart extends React.PureComponent { .filter(project => colorMetric == null || project.measures[colorMetric] !== null) .map(project => { const x = - project.measures[xMetric.key] != null ? Number(project.measures[xMetric.key]) : null; + project.measures[xMetric.key] != null ? Number(project.measures[xMetric.key]) : undefined; const y = - project.measures[yMetric.key] != null ? Number(project.measures[yMetric.key]) : null; + project.measures[yMetric.key] != null ? Number(project.measures[yMetric.key]) : undefined; const size = project.measures[sizeMetric.key] != null ? Number(project.measures[sizeMetric.key]) - : null; + : undefined; const color = colorMetric ? Number(project.measures[colorMetric]) : undefined; return { x: x || 0, @@ -110,8 +95,8 @@ export default class SimpleBubbleChart extends React.PureComponent { }; }); - const formatXTick = tick => formatMeasure(tick, xMetric.type); - const formatYTick = tick => formatMeasure(tick, yMetric.type); + const formatXTick = (tick: number) => formatMeasure(tick, xMetric.type); + const formatYTick = (tick: number) => formatMeasure(tick, yMetric.type); return ( <div> diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js b/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx index 65ce3c044d5..32070c159b5 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/Visualizations.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Risk from './Risk'; import Reliability from './Reliability'; import Security from './Security'; @@ -26,20 +25,20 @@ import Maintainability from './Maintainability'; import Coverage from './Coverage'; import Duplications from './Duplications'; import { localizeSorting } from '../utils'; +import { Project } from '../types'; import { translate, translateWithParameters } from '../../../helpers/l10n'; -export default class Visualizations extends React.PureComponent { - /*:: props: { - displayOrganizations: boolean, - projects?: Array<*>, - sort?: string, - total?: number, - visualization: string - }; -*/ +interface Props { + displayOrganizations: boolean; + projects?: Project[]; + sort?: string; + total?: number; + visualization: string; +} - renderVisualization(projects /*: Array<*> */) { - const visualizationToComponent = { +export default class Visualizations extends React.PureComponent<Props> { + renderVisualization(projects: Project[]) { + const visualizationToComponent: { [x: string]: any } = { risk: Risk, reliability: Reliability, security: Security, @@ -66,8 +65,7 @@ export default class Visualizations extends React.PureComponent { <p className="note spacer-top"> {translateWithParameters( 'projects.limited_set_of_projects', - // $FlowFixMe - projects.length, + projects!.length, localizeSorting(sort) )} </p> diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js b/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.ts index 2de9e7bebb7..3b22d353e8e 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/VisualizationsContainer.ts @@ -28,8 +28,8 @@ import { areThereCustomOrganizations } from '../../../store/rootReducer'; -const mapStateToProps = state => { - const projectKeys = getProjects(state) || []; +const mapStateToProps = (state: any) => { + const projectKeys: string[] = getProjects(state) || []; const projects = projectKeys.map(key => { const component = getComponent(state, key); return { @@ -46,4 +46,4 @@ const mapStateToProps = state => { }; }; -export default connect(mapStateToProps)(Visualizations); +export default connect<any, any, any>(mapStateToProps)(Visualizations); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Coverage-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Coverage-test.tsx new file mode 100644 index 00000000000..8e0859820c5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Coverage-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Coverage from '../Coverage'; + +it('renders', () => { + expect(shallow(<Coverage displayOrganizations={false} projects={[]} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Duplications-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Duplications-test.tsx new file mode 100644 index 00000000000..d2aaa2cca20 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Duplications-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Duplications from '../Duplications'; + +it('renders', () => { + expect(shallow(<Duplications displayOrganizations={false} projects={[]} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Maintainability-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Maintainability-test.tsx new file mode 100644 index 00000000000..1db7f9adafe --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Maintainability-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Maintainability from '../Maintainability'; + +it('renders', () => { + expect(shallow(<Maintainability displayOrganizations={false} projects={[]} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Reliability-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Reliability-test.tsx new file mode 100644 index 00000000000..93946c8ce64 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Reliability-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Reliability from '../Reliability'; + +it('renders', () => { + expect(shallow(<Reliability displayOrganizations={false} projects={[]} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx new file mode 100644 index 00000000000..dcfbc066e39 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Risk-test.tsx @@ -0,0 +1,31 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Risk from '../Risk'; + +it('renders', () => { + const project1 = { + key: 'foo', + measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' }, + name: 'Foo' + }; + expect(shallow(<Risk displayOrganizations={false} projects={[project1]} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Security-test.tsx b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Security-test.tsx new file mode 100644 index 00000000000..82d608e3e91 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Security-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import Security from '../Security'; + +it('renders', () => { + expect(shallow(<Security displayOrganizations={false} projects={[]} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/Security.js b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx index 80f4d6de997..7eda7c9762f 100644 --- a/server/sonar-web/src/main/js/apps/projects/visualizations/Security.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/SimpleBubbleChart-test.tsx @@ -1,7 +1,7 @@ /* * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 @@ -17,20 +17,26 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import SimpleBubbleChart from './SimpleBubbleChart'; +import * as React from 'react'; +import { shallow } from 'enzyme'; +import SimpleBubbleChart from '../SimpleBubbleChart'; -export default class Security extends React.PureComponent { - render() { - return ( +it('renders', () => { + const project1 = { + key: 'foo', + measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' }, + name: 'Foo' + }; + expect( + shallow( <SimpleBubbleChart - {...this.props} - xMetric={{ key: 'ncloc', type: 'SHORT_INT' }} - yMetric={{ key: 'security_remediation_effort', type: 'SHORT_WORK_DUR' }} - sizeMetric={{ key: 'vulnerabilities', type: 'SHORT_INT' }} colorMetric="security_rating" + displayOrganizations={false} + projects={[project1]} + sizeMetric={{ key: 'ncloc', type: 'INT' }} + xMetric={{ key: 'complexity', type: 'INT' }} + yMetric={{ key: 'coverage', type: 'PERCENT' }} /> - ); - } -} + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.js b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Visualizations-test.tsx index b19fabe98bc..963e0f0c259 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.js +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/Visualizations-test.tsx @@ -1,7 +1,7 @@ /* * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 @@ -17,27 +17,25 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; -import PerspectiveSelectOption from '../PerspectiveSelectOption'; +import Visualizations from '../Visualizations'; -it('should render correctly for a view', () => { +it('renders', () => { expect( - shallow( - <PerspectiveSelectOption option={{ value: 'overall', type: 'view', label: 'Overall' }}> - Overall - </PerspectiveSelectOption> - ) + shallow(<Visualizations displayOrganizations={false} projects={[]} visualization="coverage" />) ).toMatchSnapshot(); }); -it('should render correctly for a visualization', () => { +it('renders when limit is reached', () => { expect( shallow( - <PerspectiveSelectOption - option={{ value: 'coverage', type: 'visualization', label: 'Coverage' }}> - Coverage - </PerspectiveSelectOption> + <Visualizations + displayOrganizations={false} + projects={[]} + total={1000} + visualization="coverage" + /> ) ).toMatchSnapshot(); }); diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Coverage-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Coverage-test.tsx.snap new file mode 100644 index 00000000000..e1207dff93f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Coverage-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<SimpleBubbleChart + displayOrganizations={false} + projects={Array []} + sizeMetric={ + Object { + "key": "uncovered_lines", + "type": "SHORT_INT", + } + } + xMetric={ + Object { + "key": "complexity", + "type": "SHORT_INT", + } + } + yDomain={ + Array [ + 100, + 0, + ] + } + yMetric={ + Object { + "key": "coverage", + "type": "PERCENT", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Duplications-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Duplications-test.tsx.snap new file mode 100644 index 00000000000..dca1cdc0b05 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Duplications-test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<SimpleBubbleChart + displayOrganizations={false} + projects={Array []} + sizeMetric={ + Object { + "key": "duplicated_blocks", + "type": "SHORT_INT", + } + } + xMetric={ + Object { + "key": "ncloc", + "type": "SHORT_INT", + } + } + yMetric={ + Object { + "key": "duplicated_lines", + "type": "SHORT_INT", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Maintainability-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Maintainability-test.tsx.snap new file mode 100644 index 00000000000..aa9eb694998 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Maintainability-test.tsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<SimpleBubbleChart + colorMetric="sqale_rating" + displayOrganizations={false} + projects={Array []} + sizeMetric={ + Object { + "key": "code_smells", + "type": "SHORT_INT", + } + } + xMetric={ + Object { + "key": "ncloc", + "type": "SHORT_INT", + } + } + yMetric={ + Object { + "key": "sqale_index", + "type": "SHORT_WORK_DUR", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Reliability-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Reliability-test.tsx.snap new file mode 100644 index 00000000000..74b8820dcd8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Reliability-test.tsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<SimpleBubbleChart + colorMetric="reliability_rating" + displayOrganizations={false} + projects={Array []} + sizeMetric={ + Object { + "key": "bugs", + "type": "SHORT_INT", + } + } + xMetric={ + Object { + "key": "ncloc", + "type": "SHORT_INT", + } + } + yMetric={ + Object { + "key": "reliability_remediation_effort", + "type": "SHORT_WORK_DUR", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap new file mode 100644 index 00000000000..d6d546c4931 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Risk-test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div> + <BubbleChart + displayXGrid={true} + displayXTicks={true} + displayYGrid={true} + displayYTicks={true} + formatXTick={[Function]} + formatYTick={[Function]} + height={600} + items={ + Array [ + Object { + "color": undefined, + "key": "foo", + "link": Object { + "pathname": "/dashboard", + "query": Object { + "branch": undefined, + "id": "foo", + }, + }, + "size": 1734, + "tooltip": "<div class=\\"text-left\\"><div class=\\"little-spacer-bottom\\"><strong>Foo</strong></div><div>metric.reliability_rating.name: –</div><div>metric.security_rating.name: –</div><div>metric.coverage.name: 53.5%</div><div>metric.sqale_index.name: –</div><div>metric.ncloc.name: 1.7k</div></div>", + "x": 0, + "y": 53.5, + }, + ] + } + padding={ + Array [ + 80, + 20, + 60, + 100, + ] + } + sizeRange={ + Array [ + 5, + 45, + ] + } + yDomain={ + Array [ + 100, + 0, + ] + } + /> + <div + className="measure-details-bubble-chart-axis x" + > + metric.sqale_index.name + </div> + <div + className="measure-details-bubble-chart-axis y" + > + metric.coverage.name + </div> + <div + className="measure-details-bubble-chart-axis size" + > + <span + className="spacer-right" + > + component_measures.legend.color_x.projects.worse_of_reliablity_and_security + </span> + component_measures.legend.size_x.metric.ncloc.name + <ColorRatingsLegend + className="big-spacer-top" + /> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Security-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Security-test.tsx.snap new file mode 100644 index 00000000000..1ff6a7d08a7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Security-test.tsx.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<SimpleBubbleChart + colorMetric="security_rating" + displayOrganizations={false} + projects={Array []} + sizeMetric={ + Object { + "key": "vulnerabilities", + "type": "SHORT_INT", + } + } + xMetric={ + Object { + "key": "ncloc", + "type": "SHORT_INT", + } + } + yMetric={ + Object { + "key": "security_remediation_effort", + "type": "SHORT_WORK_DUR", + } + } +/> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap new file mode 100644 index 00000000000..afb4e501021 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/SimpleBubbleChart-test.tsx.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div> + <BubbleChart + displayXGrid={true} + displayXTicks={true} + displayYGrid={true} + displayYTicks={true} + formatXTick={[Function]} + formatYTick={[Function]} + height={600} + items={ + Array [ + Object { + "color": undefined, + "key": "foo", + "link": Object { + "pathname": "/dashboard", + "query": Object { + "branch": undefined, + "id": "foo", + }, + }, + "size": 1734, + "tooltip": "<div class=\\"text-left\\"><div class=\\"little-spacer-bottom\\"><strong>Foo</strong></div><div>metric.complexity.name: 17</div><div>metric.coverage.name: 53.5%</div><div>metric.ncloc.name: 1,734</div></div>", + "x": 17.2, + "y": 53.5, + }, + ] + } + padding={ + Array [ + 80, + 20, + 60, + 100, + ] + } + sizeRange={ + Array [ + 5, + 45, + ] + } + /> + <div + className="measure-details-bubble-chart-axis x" + > + metric.complexity.name + </div> + <div + className="measure-details-bubble-chart-axis y" + > + metric.coverage.name + </div> + <div + className="measure-details-bubble-chart-axis size" + > + <span + className="spacer-right" + > + component_measures.legend.color_x.metric.security_rating.name + </span> + component_measures.legend.size_x.metric.ncloc.name + <ColorRatingsLegend + className="big-spacer-top" + /> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Visualizations-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Visualizations-test.tsx.snap new file mode 100644 index 00000000000..6a28f9d5baa --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projects/visualizations/__tests__/__snapshots__/Visualizations-test.tsx.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="boxed-group projects-visualizations" +> + <div + className="projects-visualization" + > + <Coverage + displayOrganizations={false} + projects={Array []} + /> + </div> + <footer + className="projects-visualizations-footer" + > + <p> + projects.visualization.coverage.description + </p> + </footer> +</div> +`; + +exports[`renders when limit is reached 1`] = ` +<div + className="boxed-group projects-visualizations" +> + <div + className="projects-visualization" + > + <Coverage + displayOrganizations={false} + projects={Array []} + /> + </div> + <footer + className="projects-visualizations-footer" + > + <p> + projects.visualization.coverage.description + </p> + <p + className="note spacer-top" + > + projects.limited_set_of_projects.0.projects.sort.name + </p> + </footer> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js b/server/sonar-web/src/main/js/components/charts/BubbleChart.d.ts index bf478ed0a6d..e1623c22d73 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooter.js +++ b/server/sonar-web/src/main/js/components/charts/BubbleChart.d.ts @@ -1,7 +1,7 @@ /* * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 @@ -17,20 +17,32 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import ListFooter from '../../../components/controls/ListFooter'; +import * as React from 'react'; -export default class ProjectsListFooter extends React.PureComponent { - static propTypes = { - total: PropTypes.number.isRequired - }; - - render() { - if (!this.props.total) { - return null; - } +interface Item { + x: number; + y: number; + size: number; + color?: string; + key?: string; + link?: any; + tooltip?: string; +} - return <ListFooter {...this.props} />; - } +interface Props { + items: Item[]; + sizeRange?: [number, number]; + displayXGrid?: boolean; + displayXTicks?: boolean; + displayYGrid?: boolean; + displayYTicks?: boolean; + height: number; + padding: [number, number, number, number]; + formatXTick: (tick: number) => string; + formatYTick: (tick: number) => string; + onBubbleClick?: (link?: any) => void; + xDomain?: [number, number]; + yDomain?: [number, number]; } + +export default class BubbleChart extends React.Component<Props> {} diff --git a/server/sonar-web/src/main/js/components/common/EmptySearch.js b/server/sonar-web/src/main/js/components/common/EmptySearch.tsx index 719a2239c90..24a00094ada 100644 --- a/server/sonar-web/src/main/js/components/common/EmptySearch.js +++ b/server/sonar-web/src/main/js/components/common/EmptySearch.tsx @@ -17,16 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { translate } from '../../helpers/l10n'; import './EmptySearch.css'; -const EmptySearch = () => ( - <div className="empty-search"> - <h3>{translate('no_results_search')}</h3> - <p className="big-spacer-top">{translate('no_results_search.2')}</p> - </div> -); - -export default EmptySearch; +export default function EmptySearch() { + return ( + <div className="empty-search"> + <h3>{translate('no_results_search')}</h3> + <p className="big-spacer-top">{translate('no_results_search.2')}</p> + </div> + ); +} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/EmptySearch-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/EmptySearch-test.tsx new file mode 100644 index 00000000000..c9738908af1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/EmptySearch-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import EmptySearch from '../EmptySearch'; + +it('renders', () => { + expect(shallow(<EmptySearch />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/EmptySearch-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/EmptySearch-test.tsx.snap new file mode 100644 index 00000000000..34dd6bf65a4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/EmptySearch-test.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<div + className="empty-search" +> + <h3> + no_results_search + </h3> + <p + className="big-spacer-top" + > + no_results_search.2 + </p> +</div> +`; diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBase.js b/server/sonar-web/src/main/js/components/controls/FavoriteBase.js index ac1d58186b3..d35340dbec5 100644 --- a/server/sonar-web/src/main/js/components/controls/FavoriteBase.js +++ b/server/sonar-web/src/main/js/components/controls/FavoriteBase.js @@ -20,7 +20,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import FavoriteIcon from '../common/FavoriteIcon'; +import FavoriteIcon from '../icons-components/FavoriteIcon'; export default class FavoriteBase extends React.PureComponent { static propTypes = { diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.js b/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.tsx index 35260d19d85..0f63872c043 100644 --- a/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.js +++ b/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.tsx @@ -17,21 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import FavoriteIcon from '../common/FavoriteIcon'; +import * as React from 'react'; +import * as classNames from 'classnames'; +import FavoriteIcon from '../icons-components/FavoriteIcon'; -export default class FavoriteBaseStateless extends React.PureComponent { - static propTypes = { - favorite: PropTypes.bool.isRequired, - addFavorite: PropTypes.func.isRequired, - removeFavorite: PropTypes.func.isRequired, - className: PropTypes.string - }; +interface Props { + addFavorite: () => void; + className?: string; + favorite: boolean; + removeFavorite: () => void; +} - toggleFavorite = e => { - e.preventDefault(); +export default class FavoriteBaseStateless extends React.PureComponent<Props> { + toggleFavorite = (event: React.SyntheticEvent<HTMLAnchorElement>) => { + event.preventDefault(); if (this.props.favorite) { this.props.removeFavorite(); } else { diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteContainer.js b/server/sonar-web/src/main/js/components/controls/FavoriteContainer.ts index 372cb3b182e..087ace39dbb 100644 --- a/server/sonar-web/src/main/js/components/controls/FavoriteContainer.js +++ b/server/sonar-web/src/main/js/components/controls/FavoriteContainer.ts @@ -25,7 +25,7 @@ import * as api from '../../api/favorites'; import { addGlobalErrorMessage } from '../../store/globalMessages/duck'; import { parseError } from '../../apps/code/utils'; -const addFavorite = componentKey => dispatch => { +const addFavorite = (componentKey: string) => (dispatch: Function) => { // optimistic update dispatch(actionCreators.addFavorite(componentKey)); api.addFavorite(componentKey).catch(error => { @@ -34,7 +34,7 @@ const addFavorite = componentKey => dispatch => { }); }; -const removeFavorite = componentKey => dispatch => { +const removeFavorite = (componentKey: string) => (dispatch: Function) => { // optimistic update dispatch(actionCreators.removeFavorite(componentKey)); api.removeFavorite(componentKey).catch(error => { @@ -43,11 +43,11 @@ const removeFavorite = componentKey => dispatch => { }); }; -const mapStateToProps = (state, ownProps) => ({ +const mapStateToProps = (state: any, ownProps: any) => ({ favorite: isFavorite(state, ownProps.componentKey) }); -const mapDispatchToProps = (dispatch, ownProps) => ({ +const mapDispatchToProps = (dispatch: Function, ownProps: any) => ({ addFavorite: () => dispatch(addFavorite(ownProps.componentKey)), removeFavorite: () => dispatch(removeFavorite(ownProps.componentKey)) }); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx new file mode 100644 index 00000000000..d3ed6358256 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import FavoriteBaseStateless from '../FavoriteBaseStateless'; +import { click } from '../../../helpers/testUtils'; + +it('renders', () => { + expect( + shallow( + <FavoriteBaseStateless addFavorite={jest.fn()} favorite={true} removeFavorite={jest.fn()} /> + ) + ).toMatchSnapshot(); +}); + +it('adds favorite', () => { + const addFavorite = jest.fn(); + const wrapper = shallow( + <FavoriteBaseStateless addFavorite={addFavorite} favorite={false} removeFavorite={jest.fn()} /> + ); + click(wrapper); + expect(addFavorite).toBeCalled(); +}); + +it('removes favorite', () => { + const removeFavorite = jest.fn(); + const wrapper = shallow( + <FavoriteBaseStateless + addFavorite={jest.fn()} + favorite={true} + removeFavorite={removeFavorite} + /> + ); + click(wrapper); + expect(removeFavorite).toBeCalled(); +}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap new file mode 100644 index 00000000000..9edbec0cb0f --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<a + className="link-no-underline" + href="#" + onClick={[Function]} +> + <FavoriteIcon + favorite={true} + /> +</a> +`; diff --git a/server/sonar-web/src/main/js/components/common/FavoriteIcon.js b/server/sonar-web/src/main/js/components/icons-components/FavoriteIcon.tsx index 725f2ce960e..a07533ae13f 100644 --- a/server/sonar-web/src/main/js/components/common/FavoriteIcon.js +++ b/server/sonar-web/src/main/js/components/icons-components/FavoriteIcon.tsx @@ -17,34 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; -import classNames from 'classnames'; +import * as React from 'react'; +import * as classNames from 'classnames'; -/*:: -type Props = { - className?: string, - favorite: boolean, - size?: number -}; -*/ +interface Props { + className?: string; + favorite: boolean; + size?: number; +} -export default function FavoriteIcon(props /*: Props */) { - /* eslint max-len: 0 */ +export default function FavoriteIcon({ className, favorite, size = 16 }: Props) { return ( - <span - className={classNames( - 'icon-star', - { 'icon-star-favorite': props.favorite }, - props.className - )}> - <svg width={props.size} height={props.size} viewBox="0 0 16 16"> + <span className={classNames('icon-star', { 'icon-star-favorite': favorite }, className)}> + <svg width={size} height={size} viewBox="0 0 16 16"> <path d="M15.4275,5.77678C15.4275,5.90773 15.3501,6.05059 15.1953,6.20536L11.9542,9.36608L12.7221,13.8304C12.728,13.872 12.731,13.9316 12.731,14.0089C12.731,14.1339 12.6998,14.2396 12.6373,14.3259C12.5748,14.4122 12.484,14.4554 12.3649,14.4554C12.2518,14.4554 12.1328,14.4197 12.0078,14.3482L7.99888,12.2411L3.98995,14.3482C3.85901,14.4197 3.73996,14.4554 3.63281,14.4554C3.50781,14.4554 3.41406,14.4122 3.35156,14.3259C3.28906,14.2396 3.25781,14.1339 3.25781,14.0089C3.25781,13.9732 3.26377,13.9137 3.27567,13.8304L4.04353,9.36608L0.793531,6.20536C0.644719,6.04464 0.570313,5.90178 0.570313,5.77678C0.570313,5.55654 0.736979,5.41964 1.07031,5.36606L5.55245,4.71428L7.56138,0.651781C7.67447,0.407729 7.8203,0.285703 7.99888,0.285703C8.17745,0.285703 8.32328,0.407729 8.43638,0.651781L10.4453,4.71428L14.9274,5.36606C15.2608,5.41964 15.4274,5.55654 15.4274,5.77678L15.4275,5.77678Z" /> </svg> </span> ); } - -FavoriteIcon.defaultProps = { - size: 16 -}; diff --git a/server/sonar-web/src/main/js/components/measure/Measure.js b/server/sonar-web/src/main/js/components/measure/Measure.tsx index c95d52f8280..35177b49b95 100644 --- a/server/sonar-web/src/main/js/components/measure/Measure.js +++ b/server/sonar-web/src/main/js/components/measure/Measure.tsx @@ -17,26 +17,29 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Rating from '../ui/Rating'; import Level from '../ui/Level'; import Tooltips from '../controls/Tooltip'; import { formatMeasure, isDiffMetric } from '../../helpers/measures'; -import { formatLeak, getRatingTooltip } from './utils'; -/*:: import type { MeasureEnhanced } from './types'; */ +import { formatLeak, getRatingTooltip, MeasureEnhanced } from './utils'; -/*:: type Props = { - className?: string, - decimals?: ?number, - measure: MeasureEnhanced -}; */ +interface Props { + className?: string; + decimals?: number | null; + measure: MeasureEnhanced; +} -export default function Measure({ className, decimals, measure } /*: Props */) { +export default function Measure({ className, decimals, measure }: Props) { const metric = measure.metric; + const value = isDiffMetric(metric.key) ? measure.leak : measure.value; + + if (value == undefined) { + return null; + } if (metric.type === 'LEVEL') { - return <Level className={className} level={measure.value} />; + return <Level className={className} level={value} />; } if (metric.type !== 'RATING') { @@ -46,8 +49,7 @@ export default function Measure({ className, decimals, measure } /*: Props */) { return <span className={className}>{formattedValue != null ? formattedValue : '–'}</span>; } - const value = isDiffMetric(metric.key) ? measure.leak : measure.value; - const tooltip = getRatingTooltip(metric.key, value); + const tooltip = getRatingTooltip(metric.key, Number(value)); const rating = <Rating value={value} />; if (tooltip) { return ( diff --git a/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx b/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx new file mode 100644 index 00000000000..8929a8bc1c5 --- /dev/null +++ b/server/sonar-web/src/main/js/components/measure/__tests__/Measure-test.tsx @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +jest.mock('../../../helpers/measures', () => { + const measures = require.requireActual('../../../helpers/measures'); + measures.getRatingTooltip = jest.fn(() => 'tooltip'); + return measures; +}); + +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Measure from '../Measure'; + +it('renders trivial measure', () => { + const measure = { metric: { key: 'coverage', name: 'Coverage', type: 'PERCENT' }, value: '73.0' }; + expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); +}); + +it('renders leak measure', () => { + const measure = { + metric: { key: 'new_coverage', name: 'Coverage on New Code', type: 'PERCENT' }, + leak: '36.0' + }; + expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); +}); + +it('renders LEVEL', () => { + const measure = { + metric: { key: 'quality_gate_status', name: 'Quality Gate', type: 'LEVEL' }, + value: 'ERROR' + }; + expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); +}); + +it('renders known RATING', () => { + const measure = { + metric: { key: 'sqale_rating', name: 'Maintainability Rating', type: 'RATING' }, + value: '3' + }; + expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); +}); + +it('renders unknown RATING', () => { + const measure = { + metric: { key: 'foo_rating', name: 'Foo Rating', type: 'RATING' }, + value: '4' + }; + expect(shallow(<Measure measure={measure} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap b/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap new file mode 100644 index 00000000000..2988d9210e8 --- /dev/null +++ b/server/sonar-web/src/main/js/components/measure/__tests__/__snapshots__/Measure-test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders LEVEL 1`] = ` +<Level + level="ERROR" +/> +`; + +exports[`renders known RATING 1`] = ` +<Tooltip + overlay="tooltip" + placement="bottom" +> + <span> + <Rating + value="3" + /> + </span> +</Tooltip> +`; + +exports[`renders leak measure 1`] = ` +<span> + 36.0% +</span> +`; + +exports[`renders trivial measure 1`] = ` +<span> + 73.0% +</span> +`; + +exports[`renders unknown RATING 1`] = ` +<Rating + value="4" +/> +`; diff --git a/server/sonar-web/src/main/js/components/measure/utils.js b/server/sonar-web/src/main/js/components/measure/utils.ts index 62fef4790d0..017e709491d 100644 --- a/server/sonar-web/src/main/js/components/measure/utils.js +++ b/server/sonar-web/src/main/js/components/measure/utils.ts @@ -17,22 +17,34 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow import { formatMeasure, formatMeasureVariation, getRatingTooltip as nextGetRatingTooltip, isDiffMetric } from '../../helpers/measures'; -/*:: import type { Measure, MeasureEnhanced } from './types'; */ -/*:: import type { Metric } from '../../store/metrics/actions'; */ +import { Metric } from '../../app/types'; const KNOWN_RATINGS = ['sqale_rating', 'reliability_rating', 'security_rating']; +export interface MeasureIntern { + value?: string; + periods?: Array<{ index: number; value: string }>; +} + +export interface Measure extends MeasureIntern { + metric: string; +} + +export interface MeasureEnhanced extends MeasureIntern { + metric: Metric; + leak?: string | undefined | undefined; +} + export function enhanceMeasure( - measure /*: Measure */, - metrics /*: { [string]: Metric } */ -) /*: MeasureEnhanced */ { + measure: Measure, + metrics: { [key: string]: Metric } +): MeasureEnhanced { return { value: measure.value, periods: measure.periods, @@ -41,7 +53,7 @@ export function enhanceMeasure( }; } -export function formatLeak(value /*: ?string*/, metric /*: Metric*/, options /*: Object*/) { +export function formatLeak(value: string | undefined, metric: Metric, options: any): string { if (isDiffMetric(metric.key)) { return formatMeasure(value, metric.type, options); } else { @@ -49,18 +61,18 @@ export function formatLeak(value /*: ?string*/, metric /*: Metric*/, options /*: } } -export function getLeakValue(measure /*: ?Measure*/) /*: ?string*/ { +export function getLeakValue(measure: Measure | undefined): string | undefined { if (!measure || !measure.periods) { - return null; + return undefined; } const period = measure.periods.find(period => period.index === 1); - return period ? period.value : null; + return period && period.value; } -export function getRatingTooltip(metricKey /*: string */, value /*: ?string*/) { +export function getRatingTooltip(metricKey: string, value: number): string | undefined { const finalMetricKey = isDiffMetric(metricKey) ? metricKey.substr(4) : metricKey; if (KNOWN_RATINGS.includes(finalMetricKey)) { return nextGetRatingTooltip(finalMetricKey, value); } - return null; + return undefined; } diff --git a/server/sonar-web/src/main/js/components/shared/Organization.js b/server/sonar-web/src/main/js/components/shared/Organization.tsx index 1c3d5abf574..f411de59496 100644 --- a/server/sonar-web/src/main/js/components/shared/Organization.js +++ b/server/sonar-web/src/main/js/components/shared/Organization.tsx @@ -17,62 +17,48 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { connect } from 'react-redux'; import { getOrganizationByKey, areThereCustomOrganizations } from '../../store/rootReducer'; import OrganizationLink from '../ui/OrganizationLink'; -/*:: -type OwnProps = { - organizationKey: string -}; -*/ - -/*:: -type Props = { - link?: boolean, - linkClassName?: string, - organizationKey: string, - organization: { key: string, name: string } | null, - shouldBeDisplayed: boolean -}; -*/ - -class Organization extends React.PureComponent { - /*:: props: Props; */ - - static defaultProps = { - link: true - }; +interface OwnProps { + organizationKey: string; +} - render() { - const { organization, shouldBeDisplayed } = this.props; +interface Props { + link?: boolean; + linkClassName?: string; + organization: { key: string; name: string } | null; + shouldBeDisplayed: boolean; +} - if (!shouldBeDisplayed || !organization) { - return null; - } +function Organization(props: Props) { + const { link = true, organization, shouldBeDisplayed } = props; - return ( - <span> - {this.props.link ? ( - <OrganizationLink className={this.props.linkClassName} organization={organization}> - {organization.name} - </OrganizationLink> - ) : ( - organization.name - )} - <span className="slash-separator" /> - </span> - ); + if (!shouldBeDisplayed || !organization) { + return null; } + + return ( + <span> + {link ? ( + <OrganizationLink className={props.linkClassName} organization={organization}> + {organization.name} + </OrganizationLink> + ) : ( + organization.name + )} + <span className="slash-separator" /> + </span> + ); } -const mapStateToProps = (state, ownProps /*: OwnProps */) => ({ +const mapStateToProps = (state: any, ownProps: OwnProps) => ({ organization: getOrganizationByKey(state, ownProps.organizationKey), shouldBeDisplayed: areThereCustomOrganizations(state) }); -export default connect(mapStateToProps)(Organization); +export default connect<any, any, any>(mapStateToProps)(Organization); export const UnconnectedOrganization = Organization; diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/Organization-test.js b/server/sonar-web/src/main/js/components/shared/__tests__/Organization-test.tsx index 7bb6ae80476..34f6ace01b2 100644 --- a/server/sonar-web/src/main/js/components/shared/__tests__/Organization-test.js +++ b/server/sonar-web/src/main/js/components/shared/__tests__/Organization-test.tsx @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; +import * as React from 'react'; import { shallow } from 'enzyme'; import { UnconnectedOrganization } from '../Organization'; diff --git a/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/Organization-test.js.snap b/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/Organization-test.tsx.snap index 72ffd1b6d84..72ffd1b6d84 100644 --- a/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/Organization-test.js.snap +++ b/server/sonar-web/src/main/js/components/shared/__tests__/__snapshots__/Organization-test.tsx.snap diff --git a/server/sonar-web/src/main/js/components/tags/TagsList.js b/server/sonar-web/src/main/js/components/tags/TagsList.js deleted file mode 100644 index c85ad16c00a..00000000000 --- a/server/sonar-web/src/main/js/components/tags/TagsList.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import React from 'react'; -import classNames from 'classnames'; -import './TagsList.css'; - -/*:: -type Props = { - tags: Array<string>, - allowUpdate: boolean, - customClass?: string -}; -*/ - -export default class TagsList extends React.PureComponent { - /*:: props: Props; */ - - static defaultProps = { - allowUpdate: false - }; - - render() { - const { tags, allowUpdate } = this.props; - const spanClass = classNames('text-ellipsis', { note: !allowUpdate }); - const tagListClass = classNames('tags-list', this.props.customClass); - - return ( - <span className={tagListClass} title={tags.join(', ')}> - <i className="icon-tags icon-half-transparent" /> - <span className={spanClass}>{tags.join(', ')}</span> - {allowUpdate && <i className="icon-dropdown" />} - </span> - ); - } -} diff --git a/server/sonar-web/src/main/js/components/tags/TagsList.tsx b/server/sonar-web/src/main/js/components/tags/TagsList.tsx new file mode 100644 index 00000000000..a35b0465358 --- /dev/null +++ b/server/sonar-web/src/main/js/components/tags/TagsList.tsx @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 * as classNames from 'classnames'; +import './TagsList.css'; + +interface Props { + allowUpdate?: boolean; + customClass?: string; + tags: string[]; +} + +export default function TagsList({ allowUpdate = false, customClass, tags }: Props) { + const spanClass = classNames('text-ellipsis', { note: !allowUpdate }); + const tagListClass = classNames('tags-list', customClass); + + return ( + <span className={tagListClass} title={tags.join(', ')}> + <i className="icon-tags icon-half-transparent" /> + <span className={spanClass}>{tags.join(', ')}</span> + {allowUpdate && <i className="icon-dropdown" />} + </span> + ); +} diff --git a/server/sonar-web/src/main/js/components/tags/__tests__/TagsList-test.js b/server/sonar-web/src/main/js/components/tags/__tests__/TagsList-test.tsx index 438730aff86..d093e16e7fa 100644 --- a/server/sonar-web/src/main/js/components/tags/__tests__/TagsList-test.js +++ b/server/sonar-web/src/main/js/components/tags/__tests__/TagsList-test.tsx @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import * as React from 'react'; import { shallow } from 'enzyme'; -import React from 'react'; import TagsList from '../TagsList'; const tags = ['foo', 'bar']; @@ -30,14 +30,10 @@ it('should render with a list of tag', () => { expect(taglist.find('span.note').hasClass('text-ellipsis')).toBe(true); }); -it('should FAIL to render without tags', () => { - expect(() => shallow(<TagsList />)).toThrow(); -}); - it('should correctly handle a lot of tags', () => { const lotOfTags = []; for (let i = 0; i < 20; i++) { - lotOfTags.push(tags); + lotOfTags.push(String(tags)); } const taglist = shallow(<TagsList tags={lotOfTags} />); expect(taglist.text()).toBe(lotOfTags.join(', ')); diff --git a/server/sonar-web/src/main/js/components/ui/CoverageRating.js b/server/sonar-web/src/main/js/components/ui/CoverageRating.js deleted file mode 100644 index 6fe1c7cc09e..00000000000 --- a/server/sonar-web/src/main/js/components/ui/CoverageRating.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import React from 'react'; -import { DonutChart } from '../charts/donut-chart'; - -const SIZE_TO_WIDTH_MAPPING = { - small: 16, - normal: 24, - big: 40, - huge: 60 -}; - -const SIZE_TO_THICKNESS_MAPPING = { - small: 2, - normal: 3, - big: 3, - huge: 4 -}; - -export default class CoverageRating extends React.PureComponent { - /*:: props: { - value: number | string, - size?: 'small' | 'normal' | 'big' | 'huge', - muted?: boolean - }; -*/ - - static defaultProps = { - size: 'normal', - muted: false - }; - - render() { - let data = [{ value: 100, fill: '#ccc ' }]; - - if (this.props.value != null) { - const value = Number(this.props.value); - data = [ - { value, fill: this.props.muted ? '#bdbdbd' : '#00aa00' }, - { value: 100 - value, fill: this.props.muted ? '#f3f3f3' : '#d4333f' } - ]; - } - - // $FlowFixMe - const size = SIZE_TO_WIDTH_MAPPING[this.props.size]; - - // $FlowFixMe - const thickness = SIZE_TO_THICKNESS_MAPPING[this.props.size]; - - return <DonutChart data={data} width={size} height={size} thickness={thickness} />; - } -} diff --git a/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx new file mode 100644 index 00000000000..e7eda495d7f --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/CoverageRating.tsx @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 { DonutChart } from '../charts/donut-chart'; + +const SIZE_TO_WIDTH_MAPPING = { small: 16, normal: 24, big: 40, huge: 60 }; + +const SIZE_TO_THICKNESS_MAPPING = { small: 2, normal: 3, big: 3, huge: 4 }; + +interface Props { + muted?: boolean; + size?: 'small' | 'normal' | 'big' | 'huge'; + value: number | string | null | undefined; +} + +export default function CoverageRating({ muted = false, size = 'normal', value }: Props) { + let data = [{ value: 100, fill: '#ccc ' }]; + + if (value != null) { + const numberValue = Number(value); + data = [ + { value: numberValue, fill: muted ? '#bdbdbd' : '#00aa00' }, + { value: 100 - numberValue, fill: muted ? '#f3f3f3' : '#d4333f' } + ]; + } + + const width = SIZE_TO_WIDTH_MAPPING[size]; + const thickness = SIZE_TO_THICKNESS_MAPPING[size]; + + return <DonutChart data={data} width={width} height={width} thickness={thickness} />; +} diff --git a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.js b/server/sonar-web/src/main/js/components/ui/DuplicationsRating.js deleted file mode 100644 index 628adac1d1b..00000000000 --- a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import React from 'react'; -import classNames from 'classnames'; -import { inRange } from 'lodash'; -import './DuplicationsRating.css'; - -export default class DuplicationsRating extends React.PureComponent { - /*:: props: { - value: number, - size?: 'small' | 'normal' | 'big' | 'huge', - muted?: boolean - }; -*/ - - static defaultProps = { - small: false, - muted: false - }; - - render() { - const { value, size, muted } = this.props; - const className = classNames('duplications-rating', { - 'duplications-rating-small': size === 'small', - 'duplications-rating-big': size === 'big', - 'duplications-rating-huge': size === 'huge', - 'duplications-rating-muted': muted, - 'duplications-rating-A': inRange(value, 0, 3), - 'duplications-rating-B': inRange(value, 3, 5), - 'duplications-rating-C': inRange(value, 5, 10), - 'duplications-rating-D': inRange(value, 10, 20), - 'duplications-rating-E': value >= 20 - }); - - return <div className={className} />; - } -} diff --git a/server/sonar-web/src/main/js/components/ui/DuplicationsRating.tsx b/server/sonar-web/src/main/js/components/ui/DuplicationsRating.tsx new file mode 100644 index 00000000000..01703bafa31 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/DuplicationsRating.tsx @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 * as classNames from 'classnames'; +import { inRange } from 'lodash'; +import './DuplicationsRating.css'; + +interface Props { + muted?: boolean; + size?: 'small' | 'normal' | 'big' | 'huge'; + value: number; +} + +export default function DuplicationsRating({ muted = false, size = 'normal', value }: Props) { + const className = classNames('duplications-rating', { + 'duplications-rating-small': size === 'small', + 'duplications-rating-big': size === 'big', + 'duplications-rating-huge': size === 'huge', + 'duplications-rating-muted': muted, + 'duplications-rating-A': inRange(value, 0, 3), + 'duplications-rating-B': inRange(value, 3, 5), + 'duplications-rating-C': inRange(value, 5, 10), + 'duplications-rating-D': inRange(value, 10, 20), + 'duplications-rating-E': value >= 20 + }); + + return <div className={className} />; +} diff --git a/server/sonar-web/src/main/js/components/ui/OrganizationLink.js b/server/sonar-web/src/main/js/components/ui/OrganizationLink.tsx index 9251d1ae863..a5cc9d56804 100644 --- a/server/sonar-web/src/main/js/components/ui/OrganizationLink.js +++ b/server/sonar-web/src/main/js/components/ui/OrganizationLink.tsx @@ -17,18 +17,16 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { Link } from 'react-router'; -export default function OrganizationLink( - props /*: { - children?: React.Element<*>, - organization: { - key: string - } -} */ -) { +interface Props { + children?: React.ReactNode; + organization: { key: string }; + [x: string]: any; +} + +export default function OrganizationLink(props: Props) { const { children, organization, ...other } = props; return ( diff --git a/server/sonar-web/src/main/js/components/ui/Rating.js b/server/sonar-web/src/main/js/components/ui/Rating.js deleted file mode 100644 index 0ec18df9e7c..00000000000 --- a/server/sonar-web/src/main/js/components/ui/Rating.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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 React from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { formatMeasure } from '../../helpers/measures'; -import './Rating.css'; - -export default class Rating extends React.PureComponent { - static propTypes = { - className: PropTypes.string, - value: (props, propName, componentName) => { - // allow both numbers and strings - const numberValue = Number(props[propName]); - if (numberValue < 1 || numberValue > 5) { - throw new Error(`Invalid prop "${propName}" passed to "${componentName}".`); - } - }, - small: PropTypes.bool, - muted: PropTypes.bool - }; - - static defaultProps = { - small: false, - muted: false - }; - - render() { - const formatted = formatMeasure(this.props.value, 'RATING'); - const className = classNames( - 'rating', - 'rating-' + formatted, - { - 'rating-small': this.props.small, - 'rating-muted': this.props.muted - }, - this.props.className - ); - return <span className={className}>{formatted}</span>; - } -} diff --git a/server/sonar-web/src/main/js/components/ui/Rating.tsx b/server/sonar-web/src/main/js/components/ui/Rating.tsx new file mode 100644 index 00000000000..67abfe61782 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/Rating.tsx @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 * as classNames from 'classnames'; +import { formatMeasure } from '../../helpers/measures'; +import './Rating.css'; + +interface Props { + className?: string; + muted?: boolean; + small?: boolean; + value: string | number; +} + +export default function Rating({ className, muted = false, small = false, value }: Props) { + const formatted = formatMeasure(value, 'RATING'); + return ( + <span + className={classNames( + 'rating', + 'rating-' + formatted, + { 'rating-small': small, 'rating-muted': muted }, + className + )}> + {formatted} + </span> + ); +} diff --git a/server/sonar-web/src/main/js/components/ui/SizeRating.js b/server/sonar-web/src/main/js/components/ui/SizeRating.js deleted file mode 100644 index 3518c901225..00000000000 --- a/server/sonar-web/src/main/js/components/ui/SizeRating.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 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. - */ -// @flow -import React from 'react'; -import classNames from 'classnames'; -import { inRange } from 'lodash'; -import './SizeRating.css'; - -export default class SizeRating extends React.PureComponent { - /*:: props: { - value: number, - small?: boolean, - muted?: boolean - }; -*/ - - static defaultProps = { - small: false, - muted: false - }; - - render() { - const { value } = this.props; - - if (value == null) { - return <div className="size-rating size-rating-muted"> </div>; - } - - let letter; - if (inRange(value, 0, 1000)) { - letter = 'XS'; - } else if (inRange(value, 1000, 10000)) { - letter = 'S'; - } else if (inRange(value, 10000, 100000)) { - letter = 'M'; - } else if (inRange(value, 100000, 500000)) { - letter = 'L'; - } else if (value >= 500000) { - letter = 'XL'; - } - - const className = classNames('size-rating', { - 'size-rating-small': this.props.small, - 'size-rating-muted': this.props.muted - }); - - return <div className={className}>{letter}</div>; - } -} diff --git a/server/sonar-web/src/main/js/components/ui/SizeRating.tsx b/server/sonar-web/src/main/js/components/ui/SizeRating.tsx new file mode 100644 index 00000000000..d4a657676c0 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/SizeRating.tsx @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 * as classNames from 'classnames'; +import { inRange } from 'lodash'; +import './SizeRating.css'; + +interface Props { + muted?: boolean; + small?: boolean; + value: number | null | undefined; +} + +export default function SizeRating({ small = false, muted = false, value }: Props) { + if (value == null) { + return <div className="size-rating size-rating-muted"> </div>; + } + + let letter; + if (inRange(value, 0, 1000)) { + letter = 'XS'; + } else if (inRange(value, 1000, 10000)) { + letter = 'S'; + } else if (inRange(value, 10000, 100000)) { + letter = 'M'; + } else if (inRange(value, 100000, 500000)) { + letter = 'L'; + } else if (value >= 500000) { + letter = 'XL'; + } + + const className = classNames('size-rating', { + 'size-rating-small': small, + 'size-rating-muted': muted + }); + + return <div className={className}>{letter}</div>; +} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationLink-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationLink-test.tsx new file mode 100644 index 00000000000..feffbe8c87f --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/OrganizationLink-test.tsx @@ -0,0 +1,26 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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 { shallow } from 'enzyme'; +import OrganizationLink from '../OrganizationLink'; + +it('renders', () => { + expect(shallow(<OrganizationLink organization={{ key: 'org' }} />)).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationLink-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationLink-test.tsx.snap new file mode 100644 index 00000000000..147ffccb64f --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/OrganizationLink-test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders 1`] = ` +<Link + onlyActiveOnIndex={false} + style={Object {}} + to="/organizations/org" +/> +`; |