aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/components/controls
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-06-10 17:37:21 +0200
committerGitHub <noreply@github.com>2016-06-10 17:37:21 +0200
commitc6c7bf08a06b6884785cc12e5d39e20ed9b3a36b (patch)
tree856f132b1fff3687ef27d1fbca4f708884661521 /server/sonar-web/src/main/js/components/controls
parentc321ee96905615ae8608e3047c5a5f0695e65bf2 (diff)
downloadsonarqube-c6c7bf08a06b6884785cc12e5d39e20ed9b3a36b.tar.gz
sonarqube-c6c7bf08a06b6884785cc12e5d39e20ed9b3a36b.zip
refactor react components (#1033)
Diffstat (limited to 'server/sonar-web/src/main/js/components/controls')
-rw-r--r--server/sonar-web/src/main/js/components/controls/Checkbox.js56
-rw-r--r--server/sonar-web/src/main/js/components/controls/Favorite.js38
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteBase.js92
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js40
-rw-r--r--server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js40
-rw-r--r--server/sonar-web/src/main/js/components/controls/ListFooter.js74
-rw-r--r--server/sonar-web/src/main/js/components/controls/RadioToggle.js73
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js75
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js70
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js61
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js60
11 files changed, 679 insertions, 0 deletions
diff --git a/server/sonar-web/src/main/js/components/controls/Checkbox.js b/server/sonar-web/src/main/js/components/controls/Checkbox.js
new file mode 100644
index 00000000000..f417c875bf7
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/Checkbox.js
@@ -0,0 +1,56 @@
+/*
+ * 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 React from 'react';
+import classNames from 'classnames';
+
+export default class Checkbox extends React.Component {
+ static propTypes = {
+ onCheck: React.PropTypes.func.isRequired,
+ checked: React.PropTypes.bool.isRequired,
+ thirdState: React.PropTypes.bool
+ };
+
+ static defaultProps = {
+ thirdState: false
+ };
+
+ componentWillMount () {
+ this.handleClick = this.handleClick.bind(this);
+ }
+
+ handleClick (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.onCheck(!this.props.checked);
+ }
+
+ render () {
+ const className = classNames('icon-checkbox', {
+ 'icon-checkbox-checked': this.props.checked,
+ 'icon-checkbox-single': this.props.thirdState
+ });
+
+ return (
+ <a className={className}
+ href="#"
+ onClick={this.handleClick}/>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/Favorite.js b/server/sonar-web/src/main/js/components/controls/Favorite.js
new file mode 100644
index 00000000000..5e35ba2f1b2
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/Favorite.js
@@ -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 React from 'react';
+import FavoriteBase from './FavoriteBase';
+import { addFavorite, removeFavorite } from '../../api/favorites';
+
+export default class Favorite extends React.Component {
+ static propTypes = {
+ favorite: React.PropTypes.bool.isRequired,
+ component: React.PropTypes.string.isRequired
+ };
+
+ render () {
+ return (
+ <FavoriteBase
+ favorite={this.props.favorite}
+ addFavorite={() => addFavorite(this.props.component)}
+ removeFavorite={() => removeFavorite(this.props.component)}/>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBase.js b/server/sonar-web/src/main/js/components/controls/FavoriteBase.js
new file mode 100644
index 00000000000..5d1f68361be
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteBase.js
@@ -0,0 +1,92 @@
+/*
+ * 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 React from 'react';
+import classNames from 'classnames';
+
+export default class FavoriteBase extends React.Component {
+ static propTypes = {
+ favorite: React.PropTypes.bool.isRequired,
+ addFavorite: React.PropTypes.func.isRequired,
+ removeFavorite: React.PropTypes.func.isRequired
+ };
+
+ constructor (props) {
+ super(props);
+ this.state = { favorite: this.props.favorite };
+ }
+
+ componentWillMount () {
+ this.mounted = true;
+ this.toggleFavorite = this.toggleFavorite.bind(this);
+ }
+
+ componentWillUnmount () {
+ this.mounted = false;
+ }
+
+ toggleFavorite (e) {
+ e.preventDefault();
+ if (this.state.favorite) {
+ this.removeFavorite();
+ } else {
+ this.addFavorite();
+ }
+ }
+
+ addFavorite () {
+ this.props.addFavorite().then(() => {
+ if (this.mounted) {
+ this.setState({ favorite: true });
+ }
+ });
+ }
+
+ removeFavorite () {
+ this.props.removeFavorite().then(() => {
+ if (this.mounted) {
+ this.setState({ favorite: false });
+ }
+ });
+ }
+
+ renderSVG () {
+ /* jscs:disable maximumLineLength */
+ return (
+ <svg width="16" height="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>
+ );
+ }
+
+ render () {
+ const className = classNames('icon-star', {
+ 'icon-star-favorite': this.state.favorite
+ });
+
+ return (
+ <a className={className}
+ href="#"
+ onClick={this.toggleFavorite}>
+ {this.renderSVG()}
+ </a>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js b/server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js
new file mode 100644
index 00000000000..d4296548a1f
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteIssueFilter.js
@@ -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 React from 'react';
+import FavoriteBase from './FavoriteBase';
+import { toggleIssueFilter } from '../../api/issue-filters';
+
+export default class FavoriteIssueFilter extends React.Component {
+ static propTypes = {
+ favorite: React.PropTypes.bool.isRequired,
+ filter: React.PropTypes.shape({
+ id: React.PropTypes.string.isRequired
+ }).isRequired
+ };
+
+ render () {
+ return (
+ <FavoriteBase
+ favorite={this.props.favorite}
+ addFavorite={() => toggleIssueFilter(this.props.filter.id)}
+ removeFavorite={() => toggleIssueFilter(this.props.filter.id)}/>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js b/server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js
new file mode 100644
index 00000000000..f4ee1236cc9
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/FavoriteMeasureFilter.js
@@ -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 React from 'react';
+import FavoriteBase from './FavoriteBase';
+import { toggleMeasureFilter } from '../../api/measure-filters';
+
+export default class FavoriteMeasureFilter extends React.Component {
+ static propTypes = {
+ favorite: React.PropTypes.bool.isRequired,
+ filter: React.PropTypes.shape({
+ id: React.PropTypes.number.isRequired
+ }).isRequired
+ };
+
+ render () {
+ return (
+ <FavoriteBase
+ favorite={this.props.favorite}
+ addFavorite={() => toggleMeasureFilter(this.props.filter.id)}
+ removeFavorite={() => toggleMeasureFilter(this.props.filter.id)}/>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/ListFooter.js b/server/sonar-web/src/main/js/components/controls/ListFooter.js
new file mode 100644
index 00000000000..56e3b3ea6bd
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/ListFooter.js
@@ -0,0 +1,74 @@
+/*
+ * 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 classNames from 'classnames';
+import React from 'react';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+import { formatMeasure } from '../../helpers/measures';
+
+export default class ListFooter extends React.Component {
+ static propTypes = {
+ count: React.PropTypes.number.isRequired,
+ total: React.PropTypes.number.isRequired,
+ loadMore: React.PropTypes.func,
+ ready: React.PropTypes.bool
+ };
+
+ static defaultProps = {
+ ready: true
+ };
+
+ componentWillMount () {
+ this.handleLoadMore = this.handleLoadMore.bind(this);
+ }
+
+ canLoadMore () {
+ return typeof this.props.loadMore === 'function';
+ }
+
+ handleLoadMore (e) {
+ e.preventDefault();
+ e.target.blur();
+ this.props.loadMore();
+ }
+
+ render () {
+ const hasMore = this.props.total > this.props.count;
+ const loadMoreLink = (
+ <a className="spacer-left" href="#" onClick={this.handleLoadMore}>
+ {translate('show_more')}
+ </a>
+
+ );
+ const className = classNames('spacer-top note text-center', {
+ 'new-loading': !this.props.ready
+ });
+
+ return (
+ <footer className={className}>
+ {translateWithParameters(
+ 'x_of_y_shown',
+ formatMeasure(this.props.count, 'INT'),
+ formatMeasure(this.props.total, 'INT')
+ )}
+ {this.canLoadMore() && hasMore ? loadMoreLink : null}
+ </footer>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/RadioToggle.js b/server/sonar-web/src/main/js/components/controls/RadioToggle.js
new file mode 100644
index 00000000000..fe082e5a9e3
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/RadioToggle.js
@@ -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 React from 'react';
+
+export default class RadioToggle extends React.Component {
+ static propTypes = {
+ value: React.PropTypes.string,
+ options: React.PropTypes.arrayOf(React.PropTypes.shape({
+ value: React.PropTypes.string.isRequired,
+ label: React.PropTypes.string.isRequired
+ })).isRequired,
+ name: React.PropTypes.string.isRequired,
+ onCheck: React.PropTypes.func.isRequired
+ };
+
+ static defaultProps = {
+ disabled: false,
+ value: null
+ };
+
+ componentWillMount () {
+ this.renderOption = this.renderOption.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ }
+
+ handleChange (e) {
+ const newValue = e.currentTarget.value;
+ this.props.onCheck(newValue);
+ }
+
+ renderOption (option) {
+ const checked = option.value === this.props.value;
+ const htmlId = this.props.name + '__' + option.value;
+ return (
+ <li key={option.value}>
+ <input
+ type="radio"
+ name={this.props.name}
+ value={option.value}
+ id={htmlId}
+ checked={checked}
+ onChange={this.handleChange}/>
+
+ <label htmlFor={htmlId}>{option.label}</label>
+ </li>
+ );
+ }
+
+ render () {
+ return (
+ <ul className="radio-toggle">
+ {this.props.options.map(this.renderOption)}
+ </ul>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js
new file mode 100644
index 00000000000..3f26878824a
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/Checkbox-test.js
@@ -0,0 +1,75 @@
+/*
+ * 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 chai, { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
+import React from 'react';
+import Checkbox from '../Checkbox';
+
+chai.use(sinonChai);
+
+function click (element) {
+ return element.simulate('click', {
+ target: { blur () {} },
+ preventDefault () {}
+ });
+}
+
+describe('Components :: Controls :: Checkbox', () => {
+ it('should render unchecked', () => {
+ const checkbox = shallow(
+ <Checkbox checked={false} onCheck={() => true}/>
+ );
+ expect(checkbox.is('.icon-checkbox-checked')).to.equal(false);
+ });
+
+ it('should render checked', () => {
+ const checkbox = shallow(
+ <Checkbox checked={true} onCheck={() => true}/>
+ );
+ expect(checkbox.is('.icon-checkbox-checked')).to.equal(true);
+ });
+
+ it('should render unchecked third state', () => {
+ const checkbox = shallow(
+ <Checkbox checked={false} thirdState={true} onCheck={() => true}/>
+ );
+ expect(checkbox.is('.icon-checkbox-single')).to.equal(true);
+ expect(checkbox.is('.icon-checkbox-checked')).to.equal(false);
+ });
+
+ it('should render checked third state', () => {
+ const checkbox = shallow(
+ <Checkbox checked={true} thirdState={true} onCheck={() => true}/>
+ );
+ expect(checkbox.is('.icon-checkbox-single')).to.equal(true);
+ expect(checkbox.is('.icon-checkbox-checked')).to.equal(true);
+ });
+
+ it('should call onCheck', () => {
+ const onCheck = sinon.spy();
+ const checkbox = shallow(
+ <Checkbox checked={false} onCheck={onCheck}/>
+ );
+ click(checkbox);
+ expect(onCheck).to.have.been.calledWith(true);
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js
new file mode 100644
index 00000000000..6a8db197bc4
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBase-test.js
@@ -0,0 +1,70 @@
+/*
+ * 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 chai, { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
+import React from 'react';
+import FavoriteBase from '../FavoriteBase';
+
+chai.use(sinonChai);
+
+function click (element) {
+ return element.simulate('click', {
+ target: { blur () {} },
+ preventDefault () {}
+ });
+}
+
+function renderFavoriteBase (props) {
+ return shallow(
+ <FavoriteBase
+ favorite={true}
+ addFavorite={sinon.stub().throws()}
+ removeFavorite={sinon.stub().throws()}
+ {...props}/>
+ );
+}
+
+describe('Components :: Controls :: FavoriteBase', () => {
+ it('should render favorite', () => {
+ const favorite = renderFavoriteBase({ favorite: true });
+ expect(favorite.is('.icon-star-favorite')).to.equal(true);
+ });
+
+ it('should render not favorite', () => {
+ const favorite = renderFavoriteBase({ favorite: false });
+ expect(favorite.is('.icon-star-favorite')).to.equal(false);
+ });
+
+ it('should add favorite', () => {
+ const addFavorite = sinon.stub().returns(Promise.resolve());
+ const favorite = renderFavoriteBase({ favorite: false, addFavorite });
+ click(favorite.find('a'));
+ expect(addFavorite).to.have.been.called;
+ });
+
+ it('should remove favorite', () => {
+ const removeFavorite = sinon.stub().returns(Promise.resolve());
+ const favorite = renderFavoriteBase({ favorite: true, removeFavorite });
+ click(favorite.find('a'));
+ expect(removeFavorite).to.have.been.called;
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js
new file mode 100644
index 00000000000..5d02cf7ef36
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/ListFooter-test.js
@@ -0,0 +1,61 @@
+/*
+ * 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 chai, { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
+import React from 'react';
+import ListFooter from '../ListFooter';
+
+chai.use(sinonChai);
+
+function click (element) {
+ return element.simulate('click', {
+ target: { blur () {} },
+ preventDefault () {}
+ });
+}
+
+describe('Components :: Controls :: ListFooter', () => {
+ it('should render "3 of 5 shown"', () => {
+ const listFooter = shallow(
+ <ListFooter count={3} total={5}/>
+ );
+ expect(listFooter.text()).to.contain('x_of_y_shown.3.5');
+ });
+
+ it('should not render "show more"', () => {
+ const listFooter = shallow(
+ <ListFooter count={3} total={5}/>
+ );
+ expect(listFooter.find('a')).to.have.length(0);
+ });
+
+ it('should "show more"', () => {
+ const loadMore = sinon.spy();
+ const listFooter = shallow(
+ <ListFooter count={3} total={5} loadMore={loadMore}/>
+ );
+ const link = listFooter.find('a');
+ expect(link).to.have.length(1);
+ click(link);
+ expect(loadMore).to.have.been.called;
+ });
+});
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js b/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js
new file mode 100644
index 00000000000..8da0df6cca5
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/RadioToggle-test.js
@@ -0,0 +1,60 @@
+/*
+ * 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 chai, { expect } from 'chai';
+import { shallow } from 'enzyme';
+import sinon from 'sinon';
+import sinonChai from 'sinon-chai';
+import React from 'react';
+import RadioToggle from '../RadioToggle';
+
+chai.use(sinonChai);
+
+function getSample (props) {
+ const options = [
+ { value: 'one', label: 'first' },
+ { value: 'two', label: 'second' }
+ ];
+ return (
+ <RadioToggle
+ options={options}
+ name="sample"
+ onCheck={() => true}
+ {...props}/>
+ );
+}
+
+function change (element, value) {
+ return element.simulate('change', { currentTarget: { value } });
+}
+
+describe('Components :: Controls :: RadioToggle', () => {
+ it('should render', () => {
+ const radioToggle = shallow(getSample());
+ expect(radioToggle.find('input[type="radio"]')).to.have.length(2);
+ expect(radioToggle.find('label')).to.have.length(2);
+ });
+
+ it('should call onCheck', () => {
+ const onCheck = sinon.spy();
+ const radioToggle = shallow(getSample({ onCheck }));
+ change(radioToggle.find('input[value="two"]'), 'two');
+ expect(onCheck).to.have.been.calledWith('two');
+ });
+});