@@ -20,13 +20,10 @@ | |||
package it.user; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import com.sonar.orchestrator.build.SonarScanner; | |||
import com.sonar.orchestrator.selenium.Selenese; | |||
import it.Category4Suite; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.BeforeClass; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import org.junit.*; | |||
import org.junit.experimental.categories.Category; | |||
import org.sonarqube.ws.client.PostRequest; | |||
import org.sonarqube.ws.client.WsClient; | |||
@@ -34,6 +31,7 @@ import util.QaOnly; | |||
import util.selenium.SeleneseTest; | |||
import static util.ItUtils.newAdminWsClient; | |||
import static util.ItUtils.projectDir; | |||
@Category(QaOnly.class) | |||
public class MyAccountPageTest { | |||
@@ -81,6 +79,24 @@ public class MyAccountPageTest { | |||
new SeleneseTest(selenese).runOn(orchestrator); | |||
} | |||
@Test | |||
public void should_display_projects() throws Exception { | |||
// first, try on empty instance | |||
Selenese selenese = Selenese.builder().setHtmlTestsInClasspath("should_display_projects", | |||
"/user/MyAccountPageTest/should_display_no_projects.html" | |||
).build(); | |||
new SeleneseTest(selenese).runOn(orchestrator); | |||
// then, analyze a project | |||
analyzeProject("sample"); | |||
grantAdminPermission("account-user", "sample"); | |||
selenese = Selenese.builder().setHtmlTestsInClasspath("should_display_projects", | |||
"/user/MyAccountPageTest/should_display_projects.html" | |||
).build(); | |||
new SeleneseTest(selenese).runOn(orchestrator); | |||
} | |||
private static void createUser(String login, String name, String email) { | |||
adminWsClient.wsConnector().call( | |||
new PostRequest("api/users/create") | |||
@@ -96,4 +112,19 @@ public class MyAccountPageTest { | |||
.setParam("login", login)); | |||
} | |||
private static void analyzeProject(String projectKey) { | |||
SonarScanner build = SonarScanner.create(projectDir("qualityGate/xoo-sample")) | |||
.setProjectKey(projectKey) | |||
.setProperty("sonar.projectDescription", "Description of a project") | |||
.setProperty("sonar.links.homepage", "http://example.com"); | |||
orchestrator.executeBuild(build); | |||
} | |||
private static void grantAdminPermission(String login, String projectKey) { | |||
adminWsClient.wsConnector().call( | |||
new PostRequest("api/permissions/add_user") | |||
.setParam("login", login) | |||
.setParam("projectKey", projectKey) | |||
.setParam("permission", "admin")); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head profile="http://selenium-ide.openqa.org/profiles/test-case"> | |||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> | |||
<link rel="selenium.base" href="http://localhost:49506"/> | |||
<title>should_display_no_projects</title> | |||
</head> | |||
<body> | |||
<table cellpadding="1" cellspacing="1" border="1"> | |||
<thead> | |||
<tr> | |||
<td rowspan="1" colspan="3">should_display_no_projects</td> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td>open</td> | |||
<td>/sessions/login</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>id=login</td> | |||
<td>account-user</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>id=password</td> | |||
<td>password</td> | |||
</tr> | |||
<tr> | |||
<td>clickAndWait</td> | |||
<td>commit</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>open</td> | |||
<td>/account/projects</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>waitForElementPresent</td> | |||
<td>css=.account-projects</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertElementNotPresent</td> | |||
<td>css=.account-projects-list</td> | |||
<td></td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</body> | |||
</html> |
@@ -0,0 +1,80 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head profile="http://selenium-ide.openqa.org/profiles/test-case"> | |||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> | |||
<link rel="selenium.base" href="http://localhost:49506"/> | |||
<title>should_display_projects</title> | |||
</head> | |||
<body> | |||
<table cellpadding="1" cellspacing="1" border="1"> | |||
<thead> | |||
<tr> | |||
<td rowspan="1" colspan="3">should_display_projects</td> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr> | |||
<td>open</td> | |||
<td>/sessions/login</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>id=login</td> | |||
<td>account-user</td> | |||
</tr> | |||
<tr> | |||
<td>type</td> | |||
<td>id=password</td> | |||
<td>password</td> | |||
</tr> | |||
<tr> | |||
<td>clickAndWait</td> | |||
<td>commit</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>open</td> | |||
<td>/account/projects</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>waitForElementPresent</td> | |||
<td>css=.account-project-card</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>css=.account-project-name</td> | |||
<td>*Sample*</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>css=.account-project-quality-gate</td> | |||
<td>*Passed*</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>css=.account-project-key</td> | |||
<td>*sample*</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>css=.account-project-description</td> | |||
<td>*Description of a project*</td> | |||
</tr> | |||
<tr> | |||
<td>assertElementPresent</td> | |||
<td>css=.account-project-analysis</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertElementPresent</td> | |||
<td>css=.account-project-links a[href="http://example.com"]</td> | |||
<td></td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
</body> | |||
</html> |
@@ -98,3 +98,8 @@ export function getProjectsWithInternalId (query) { | |||
}; | |||
return getJSON(url, data).then(r => r.results); | |||
} | |||
export function getMyProjects (data) { | |||
const url = window.baseUrl + '/api/projects/search_my_projects'; | |||
return getJSON(url, data); | |||
} |
@@ -7,9 +7,7 @@ | |||
background-color: #f3f3f3; | |||
} | |||
.account-nav { | |||
} | |||
.account-nav { } | |||
.account-nav .nav-tabs { | |||
width: 100%; | |||
@@ -45,3 +43,73 @@ | |||
.account-bar-chart .histogram-value { | |||
text-anchor: start; | |||
} | |||
.account-projects { | |||
max-width: 600px; | |||
} | |||
.account-projects-list > li + li { | |||
margin-top: 10px; | |||
} | |||
.account-project-side { | |||
float: right; | |||
margin-left: 10px; | |||
text-align: right; | |||
} | |||
.account-project-analysis { | |||
line-height: 24px; | |||
color: #777; | |||
font-size: 12px; | |||
} | |||
.account-project-card { | |||
position: relative; | |||
display: block; | |||
padding: 10px 15px; | |||
border: 1px solid #e6e6e6; | |||
border-radius: 3px; | |||
background-color: #fff; | |||
} | |||
.account-project-name { | |||
display: inline-block; | |||
vertical-align: top; | |||
max-width: 300px; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.account-project-name > a { | |||
border-bottom-color: #d0d0d0; | |||
color: #444; | |||
} | |||
.account-project-name > a:hover { | |||
border-bottom-color: #cae3f2; | |||
color: #4b9fd5; | |||
} | |||
.account-project-quality-gate { | |||
display: inline-block; | |||
vertical-align: top; | |||
line-height: 24px; | |||
margin-left: 8px; | |||
} | |||
.account-project-description { | |||
margin-top: 6px; | |||
line-height: 1.5; | |||
} | |||
.account-project-links { | |||
margin-top: 4px; | |||
} | |||
.account-project-key { | |||
margin-top: 6px; | |||
color: #777; | |||
font-size: 12px; | |||
} |
@@ -26,6 +26,7 @@ import Home from './components/Home'; | |||
import NotificationsContainer from './components/NotificationsContainer'; | |||
import Security from './components/Security'; | |||
import Issues from './components/Issues'; | |||
import ProjectsContainer from './projects/ProjectsContainer'; | |||
window.sonarqube.appStarted.then(options => { | |||
const el = document.querySelector(options.el); | |||
@@ -41,6 +42,7 @@ window.sonarqube.appStarted.then(options => { | |||
<Route path="issues" component={Issues}/> | |||
<Route path="notifications" component={NotificationsContainer}/> | |||
<Route path="security" component={Security}/> | |||
<Route path="projects" component={ProjectsContainer}/> | |||
<Redirect from="/index" to="/"/> | |||
</Route> |
@@ -41,6 +41,11 @@ const Nav = ({ user }) => ( | |||
{translate('issues.page')} | |||
</a> | |||
</li> | |||
<li> | |||
<IndexLink to="projects" activeClassName="active"> | |||
{translate('my_account.projects')} | |||
</IndexLink> | |||
</li> | |||
<li> | |||
<IndexLink to="notifications" activeClassName="active"> | |||
{translate('my_account.notifications')} |
@@ -0,0 +1,98 @@ | |||
/* | |||
* 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 moment from 'moment'; | |||
import sortBy from 'lodash/sortBy'; | |||
import Level from '../../../components/ui/Level'; | |||
import { getComponentUrl } from '../../../helpers/urls'; | |||
import { projectType } from './propTypes'; | |||
import { translateWithParameters, translate } from '../../../helpers/l10n'; | |||
export default class ProjectCard extends React.Component { | |||
static propTypes = { | |||
project: projectType.isRequired | |||
}; | |||
render () { | |||
const { project } = this.props; | |||
const isAnalyzed = project.lastAnalysisDate != null; | |||
const analysisMoment = isAnalyzed && moment(project.lastAnalysisDate); | |||
const links = sortBy(project.links, 'type'); | |||
return ( | |||
<div className="account-project-card" href="#"> | |||
<aside className="account-project-side"> | |||
{isAnalyzed ? ( | |||
<div className="account-project-analysis" | |||
title={analysisMoment.format('LLL')}> | |||
{translateWithParameters( | |||
'my_account.projects.analyzed_x', | |||
analysisMoment.fromNow() | |||
)} | |||
</div> | |||
) : ( | |||
<div className="account-project-analysis"> | |||
{translate('my_account.projects.never_analyzed')} | |||
</div> | |||
)} | |||
{links.length > 0 && ( | |||
<div className="account-project-links"> | |||
<ul className="list-inline"> | |||
{links.map(link => ( | |||
<li key={link.type}> | |||
<a | |||
className="link-with-icon" | |||
href={link.href} | |||
title={link.name} | |||
target="_blank" | |||
rel="nofollow"> | |||
<i className={`icon-color-link icon-${link.type}`}/> | |||
</a> | |||
</li> | |||
))} | |||
</ul> | |||
</div> | |||
)} | |||
</aside> | |||
<h3 className="account-project-name"> | |||
<a href={getComponentUrl(project.key)}> | |||
{project.name} | |||
</a> | |||
</h3> | |||
{project.qualityGate != null && ( | |||
<div className="account-project-quality-gate"> | |||
<Level level={project.qualityGate}/> | |||
</div> | |||
)} | |||
<div className="account-project-key">{project.key}</div> | |||
{!!project.description && ( | |||
<div className="account-project-description"> | |||
{project.description} | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -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 React from 'react'; | |||
import ProjectCard from './ProjectCard'; | |||
import ProjectsSearch from './ProjectsSearch'; | |||
import ListFooter from '../../../components/controls/ListFooter'; | |||
import { projectsListType } from './propTypes'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default class Projects extends React.Component { | |||
static propTypes = { | |||
projects: projectsListType.isRequired, | |||
total: React.PropTypes.number.isRequired, | |||
loading: React.PropTypes.bool.isRequired, | |||
search: React.PropTypes.func.isRequired, | |||
loadMore: React.PropTypes.func.isRequired | |||
}; | |||
render () { | |||
const { projects } = this.props; | |||
return ( | |||
<div className="page page-limited account-projects"> | |||
<ProjectsSearch | |||
onSearch={this.props.search}/> | |||
{projects.length === 0 && ( | |||
<div className="js-no-results"> | |||
{translate('no_results')} | |||
</div> | |||
)} | |||
{projects.length > 0 && ( | |||
<ul className="account-projects-list"> | |||
{projects.map(project => ( | |||
<li key={project.key}> | |||
<ProjectCard project={project}/> | |||
</li> | |||
))} | |||
</ul> | |||
)} | |||
{projects.length > 0 && ( | |||
<ListFooter | |||
count={projects.length} | |||
total={this.props.total} | |||
ready={!this.props.loading} | |||
loadMore={this.props.loadMore}/> | |||
)} | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
/* | |||
* 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 Projects from './Projects'; | |||
import { getMyProjects } from '../../../api/components'; | |||
export default class ProjectsContainer extends React.Component { | |||
state = { | |||
loading: true, | |||
page: 1, | |||
query: '' | |||
}; | |||
componentWillMount () { | |||
this.loadMore = this.loadMore.bind(this); | |||
this.search = this.search.bind(this); | |||
document.querySelector('html').classList.add('dashboard-page'); | |||
} | |||
componentDidMount () { | |||
this.mounted = true; | |||
this.loadProjects(); | |||
} | |||
componentWillUnmount () { | |||
this.mounted = false; | |||
document.querySelector('html').classList.remove('dashboard-page'); | |||
} | |||
loadProjects (page = this.state.page, query = this.state.query) { | |||
this.setState({ loading: true }); | |||
const data = { ps: 20 }; // FIXME | |||
if (page > 1) { | |||
data.p = page; | |||
} | |||
if (query) { | |||
data.q = query; | |||
} | |||
return getMyProjects(data).then(r => { | |||
const projects = page > 1 ? | |||
[...this.state.projects, ...r.projects] : r.projects; | |||
this.setState({ | |||
projects, | |||
query, | |||
loading: false, | |||
page: r.paging.pageIndex, | |||
total: r.paging.total | |||
}); | |||
}); | |||
} | |||
loadMore () { | |||
return this.loadProjects(this.state.page + 1); | |||
} | |||
search (query) { | |||
return this.loadProjects(1, query); | |||
} | |||
render () { | |||
if (this.state.projects == null) { | |||
return ( | |||
<div className="text-center"> | |||
<i className="spinner spinner-margin"/> | |||
</div> | |||
); | |||
} | |||
return ( | |||
<Projects | |||
projects={this.state.projects} | |||
total={this.state.total} | |||
loading={this.state.loading} | |||
loadMore={this.loadMore} | |||
search={this.search}/> | |||
); | |||
} | |||
} |
@@ -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. | |||
*/ | |||
import React from 'react'; | |||
import debounce from 'lodash/debounce'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
export default class ProjectsSearch extends React.Component { | |||
static propTypes = { | |||
onSearch: React.PropTypes.func.isRequired | |||
}; | |||
componentWillMount () { | |||
this.handleChange = this.handleChange.bind(this); | |||
this.handleSubmit = this.handleSubmit.bind(this); | |||
this.onSearch = debounce(this.props.onSearch, 250); | |||
} | |||
handleChange () { | |||
const { value } = this.refs.input; | |||
if (value.length > 2 || value.length === 0) { | |||
this.onSearch(value); | |||
} | |||
} | |||
handleSubmit (e) { | |||
e.preventDefault(); | |||
this.handleChange(); | |||
} | |||
render () { | |||
return ( | |||
<div className="big-spacer-bottom"> | |||
<form onSubmit={this.handleSubmit}> | |||
<input | |||
ref="input" | |||
type="search" | |||
className="input-large" | |||
placeholder={translate('search_verb')} | |||
onChange={this.handleChange}/> | |||
<span className="note spacer-left"> | |||
{translateWithParameters( | |||
'my_account.projects.x_characters_min', 3)} | |||
</span> | |||
</form> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,94 @@ | |||
/* | |||
* 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 { shallow } from 'enzyme'; | |||
import { expect } from 'chai'; | |||
import ProjectCard from '../ProjectCard'; | |||
import Level from '../../../../components/ui/Level'; | |||
const BASE = { id: 'id', key: 'key', name: 'name', links: [] }; | |||
describe('My Account :: ProjectCard', () => { | |||
it('should render key and name', () => { | |||
const project = { ...BASE }; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect(output.find('.account-project-key').text()).to.equal('key'); | |||
expect(output.find('.account-project-name').text()).to.equal('name'); | |||
}); | |||
it('should render description', () => { | |||
const project = { ...BASE, description: 'bla' }; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect(output.find('.account-project-description').text()).to.equal('bla'); | |||
}); | |||
it('should not render optional fields', () => { | |||
const project = { ...BASE }; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect(output.find('.account-project-description')).to.have.length(0); | |||
expect(output.find('.account-project-quality-gate')).to.have.length(0); | |||
expect(output.find('.account-project-links')).to.have.length(0); | |||
}); | |||
it('should render analysis date', () => { | |||
const project = { ...BASE, lastAnalysisDate: '2016-05-17' }; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect(output.find('.account-project-analysis').text()) | |||
.to.contain('my_account.projects.analyzed_x'); | |||
}); | |||
it('should not render analysis date', () => { | |||
const project = { ...BASE }; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect(output.find('.account-project-analysis').text()) | |||
.to.contain('my_account.projects.never_analyzed'); | |||
}); | |||
it('should render quality gate status', () => { | |||
const project = { ...BASE, qualityGate: 'ERROR' }; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect( | |||
output.find('.account-project-quality-gate').find(Level).prop('level') | |||
).to.equal('ERROR'); | |||
}); | |||
it('should render links', () => { | |||
const project = { | |||
...BASE, | |||
links: [{ name: 'n', type: 't', href: 'h' }] | |||
}; | |||
const output = shallow( | |||
<ProjectCard project={project}/> | |||
); | |||
expect(output.find('.account-project-links').find('li')).to.have.length(1); | |||
}); | |||
}); |
@@ -0,0 +1,83 @@ | |||
/* | |||
* 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 { shallow } from 'enzyme'; | |||
import { expect } from 'chai'; | |||
import sinon from 'sinon'; | |||
import Projects from '../Projects'; | |||
import ProjectCard from '../ProjectCard'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
describe('My Account :: Projects', () => { | |||
it('should render list of ProjectCards', () => { | |||
const projects = [ | |||
{ id: 'id1', key: 'key1', name: 'name1', links: [] }, | |||
{ id: 'id2', key: 'key2', name: 'name2', links: [] } | |||
]; | |||
const output = shallow( | |||
<Projects | |||
projects={projects} | |||
total={5} | |||
loading={false} | |||
search={() => true} | |||
loadMore={() => true}/> | |||
); | |||
expect(output.find(ProjectCard)).to.have.length(2); | |||
}); | |||
it('should render ListFooter', () => { | |||
const projects = [ | |||
{ id: 'id1', key: 'key1', name: 'name1', links: [] }, | |||
{ id: 'id2', key: 'key2', name: 'name2', links: [] } | |||
]; | |||
const loadMore = sinon.stub().throws(); | |||
const footer = shallow( | |||
<Projects | |||
projects={projects} | |||
total={5} | |||
loading={false} | |||
search={() => true} | |||
loadMore={loadMore}/> | |||
).find(ListFooter); | |||
expect(footer).to.have.length(1); | |||
expect(footer.prop('count')).to.equal(2); | |||
expect(footer.prop('total')).to.equal(5); | |||
expect(footer.prop('loadMore')).to.equal(loadMore); | |||
}); | |||
it('should render when no results', () => { | |||
const output = shallow( | |||
<Projects | |||
projects={[]} | |||
total={0} | |||
loading={false} | |||
search={() => true} | |||
loadMore={() => true}/> | |||
); | |||
expect(output.find('.js-no-results')).to.have.length(1); | |||
expect(output.find(ProjectCard)).to.have.length(0); | |||
expect(output.find(ListFooter)).to.have.length(0); | |||
}); | |||
}); |
@@ -0,0 +1,34 @@ | |||
/* | |||
* 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'; | |||
const { shape, string, array, arrayOf } = React.PropTypes; | |||
export const projectType = shape({ | |||
id: string.isRequired, | |||
key: string.isRequired, | |||
name: string.isRequired, | |||
lastAnalysisDate: string, | |||
description: string, | |||
links: array.isRequired, | |||
qualityGate: string | |||
}); | |||
export const projectsListType = arrayOf(projectType); |
@@ -2122,6 +2122,10 @@ my_account.issue_widget.by_project=My Issues by Project | |||
my_account.issue_widget.by_severity=My Issues by Severity | |||
my_account.to_fix=To Fix | |||
my_account.to_review=To Review | |||
my_account.projects=Projects | |||
my_account.projects.analyzed_x=Analyzed {0} | |||
my_account.projects.never_analyzed=Never analyzed | |||
my_account.projects.x_characters_min=({0} characters min) | |||