diff options
19 files changed, 474 insertions, 15 deletions
diff --git a/server/sonar-web/src/main/js/apps/nav/app.jsx b/server/sonar-web/src/main/js/apps/nav/app.jsx index d7bcc337d81..48a708e321b 100644 --- a/server/sonar-web/src/main/js/apps/nav/app.jsx +++ b/server/sonar-web/src/main/js/apps/nav/app.jsx @@ -1,15 +1,22 @@ import React from 'react'; -import GlobalNav from './global-nav'; +import GlobalNav from './global/global-nav'; +import ComponentNav from './component/component-nav'; export default { start(options) { window.requestMessages().done(() => { this.renderGlobalNav(options); + options.space === 'component' && this.renderComponentNav(options); }); }, renderGlobalNav(options) { const el = document.getElementById('global-navigation'); React.render(<GlobalNav {...options}/>, el); + }, + + renderComponentNav(options) { + const el = document.getElementById('context-navigation'); + React.render(<ComponentNav {...options}/>, el); } }; diff --git a/server/sonar-web/src/main/js/apps/nav/component/component-nav-breadcrumbs.jsx b/server/sonar-web/src/main/js/apps/nav/component/component-nav-breadcrumbs.jsx new file mode 100644 index 00000000000..1bb643c11ac --- /dev/null +++ b/server/sonar-web/src/main/js/apps/nav/component/component-nav-breadcrumbs.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import QualifierIcon from 'components/shared/qualifier-icon'; + +export default React.createClass({ + render() { + if (!this.props.breadcrumbs) { + return null; + } + const items = this.props.breadcrumbs.map((item, index) => { + const url = `${window.baseUrl}/dashboard/index?id=${encodeURIComponent(item.key)}`; + return ( + <li key={index}> + <a href={url}> + <QualifierIcon qualifier={item.qualifier}/> {item.name} + </a> + </li> + ); + }); + return ( + <ul className="nav navbar-nav nav-crumbs">{items}</ul> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/nav/component/component-nav-favorite.jsx b/server/sonar-web/src/main/js/apps/nav/component/component-nav-favorite.jsx new file mode 100644 index 00000000000..8e2b8624abd --- /dev/null +++ b/server/sonar-web/src/main/js/apps/nav/component/component-nav-favorite.jsx @@ -0,0 +1,15 @@ +import React from 'react'; +import Favorite from 'components/shared/favorite'; + +export default React.createClass({ + render() { + if (!this.props.canBeFavorite) { + return null; + } + return ( + <div className="navbar-context-favorite"> + <Favorite component={this.props.component} favorite={this.props.favorite}/> + </div> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/nav/component/component-nav-menu.jsx b/server/sonar-web/src/main/js/apps/nav/component/component-nav-menu.jsx new file mode 100644 index 00000000000..03e1c28f7ee --- /dev/null +++ b/server/sonar-web/src/main/js/apps/nav/component/component-nav-menu.jsx @@ -0,0 +1,226 @@ +import React from 'react'; +import DashboardNameMixin from '../dashboard-name-mixin'; + +const SETTINGS_URLS = [ + '/project/settings', '/project/profile', '/project/qualitygate', '/manual_measures/index', + '/action_plans/index', '/project/links', '/project_roles/index', '/project/history', '/project/key', + '/project/deletion' +]; + +const MORE_URLS = ['/dashboards', '/dashboard', '/plugins/resource']; + +export default React.createClass({ + mixins: [DashboardNameMixin], + + activeLink(url) { + return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; + }, + + renderLink(url, title, highlightUrl = url) { + let fullUrl = window.baseUrl + url; + return ( + <li key={highlightUrl} className={this.activeLink(highlightUrl)}> + <a href={fullUrl}>{title}</a> + </li> + ); + }, + + renderOverviewLink() { + const url = `/overview/index?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('overview.page'), '/overview'); + }, + + renderComponentsLink() { + const url = `/components/index?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('components.page'), '/components'); + }, + + renderComponentIssuesLink() { + const url = `/component_issues/index?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('issues.page'), '/component_issues'); + }, + + renderAdministration() { + if (!this.props.conf.showSettings) { + return null; + } + let isSettingsActive = SETTINGS_URLS.some(url => { + return window.location.href.indexOf(url) !== -1; + }), + className = 'dropdown' + (isSettingsActive ? ' active' : ''); + return ( + <li className={className}> + <a className="dropdown-toggle navbar-admin-link" data-toggle="dropdown" href="#"> + {window.t('layout.settings')} <i className="icon-dropdown"/></a> + <ul className="dropdown-menu"> + {this.renderSettingsLink()} + {this.renderProfilesLink()} + {this.renderQualityGatesLink()} + {this.renderCustomMeasuresLink()} + {this.renderActionPlansLink()} + {this.renderLinksLink()} + {this.renderPermissionsLink()} + {this.renderHistoryLink()} + {this.renderUpdateKeyLink()} + {this.renderDeletionLink()} + {this.renderExtensions()} + </ul> + </li> + ); + }, + + renderSettingsLink() { + const url = `/project/settings?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('project_settings.page'), '/project/settings'); + }, + + renderProfilesLink() { + if (!this.props.conf.showQualityProfiles) { + return null; + } + const url = `/project/profile?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('project_quality_profiles.page'), '/project/profile'); + }, + + renderQualityGatesLink() { + if (!this.props.conf.showQualityGates) { + return null; + } + const url = `/project/qualitygate?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('project_quality_gate.page'), '/project/qualitygate'); + }, + + renderCustomMeasuresLink() { + if (!this.props.conf.showManualMeasures) { + return null; + } + const url = `/custom_measures?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('custom_measures.page'), '/custom_measures'); + }, + + renderActionPlansLink() { + if (!this.props.conf.showActionPlans) { + return null; + } + const url = `/action_plans?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('action_plans.page'), '/action_plans'); + }, + + renderLinksLink() { + if (!this.props.conf.showLinks) { + return null; + } + const url = `/project/links?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('project_links.page'), '/project/links'); + }, + + renderPermissionsLink() { + if (!this.props.conf.showPermissions) { + return null; + } + const url = `/project_roles?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('permissions.page'), '/project_roles'); + }, + + renderHistoryLink() { + if (!this.props.conf.showHistory) { + return null; + } + const url = `/project/history?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('project_history.page'), '/project/history'); + }, + + renderUpdateKeyLink() { + if (!this.props.conf.showUpdateKey) { + return null; + } + const url = `/project/key?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('update_key.page'), '/project/key'); + }, + + renderDeletionLink() { + if (!this.props.conf.showDeletion) { + return null; + } + const url = `/project/deletion?id=${encodeURIComponent(this.props.component.key)}`; + return this.renderLink(url, window.t('deletion.page'), '/project/deletion'); + }, + + renderExtensions() { + let extensions = this.props.conf.extensions || []; + return extensions.map(e => { + return this.renderLink(e.url, e.name, e.url); + }); + }, + + renderMore() { + let isActive = MORE_URLS.some(url => { + return window.location.href.indexOf(url) !== -1; + }), + className = 'dropdown' + (isActive ? ' active' : ''); + return ( + <li className={className}> + <a className="dropdown-toggle" data-toggle="dropdown" href="#"> + {window.t('more')} <i className="icon-dropdown"></i> + </a> + <ul className="dropdown-menu"> + {this.renderDashboards()} + {this.renderDashboardManagementLink()} + {this.renderTools()} + </ul> + </li> + ); + }, + + renderDashboards() { + let dashboards = (this.props.component.dashboards || []).map(d => { + let url = `${window.baseUrl}/dashboard?id=${encodeURIComponent(this.props.component.key)}&did=${d.key}`; + let name = this.getLocalizedDashboardName(d.name); + return this.renderLink(url, name); + }); + return [<li key="0" className="dropdown-header">{window.t('layout.dashboards')}</li>].concat(dashboards); + }, + + renderDashboardManagementLink() { + if (!window.SS.user) { + return null; + } + let url = `${window.baseUrl}/dashboards?resource=${encodeURIComponent(this.props.component.key)}`; + let name = window.t('dashboard.manage_dashboards'); + return [ + <li key="dashboard-divider" className="small-divider"></li>, + this.renderLink(url, name, '/dashboards') + ]; + }, + + renderTools() { + let component = this.props.component; + if (!component.isComparable && !_.size(component.extensions)) { + return null; + } + let tools = [ + <li key="tools-divider" className="divider"></li>, + <li key="tools" className="dropdown-header">Tools</li> + ]; + if (component.isComparable) { + let compareUrl = `/comparison/index?resource=${component.key}`; + tools.push(this.renderLink(compareUrl, window.t('comparison.page'))); + } + (component.extensions || []).forEach(e => { + tools.push(this.renderLink(e.url, e.name)); + }); + return tools; + }, + + render() { + return ( + <ul className="nav navbar-nav nav-tabs"> + {this.renderOverviewLink()} + {this.renderComponentsLink()} + {this.renderComponentIssuesLink()} + {this.renderAdministration()} + {this.renderMore()} + </ul> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/nav/component/component-nav-meta.jsx b/server/sonar-web/src/main/js/apps/nav/component/component-nav-meta.jsx new file mode 100644 index 00000000000..8ebb8e91a42 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/nav/component/component-nav-meta.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export default React.createClass({ + render() { + const version = this.props.version ? `Version ${this.props.version}` : null; + const snapshotDate = this.props.snapshotDate ? moment(this.props.snapshotDate).format('LLL') : null; + return ( + <div className="navbar-right navbar-context-meta"> + {version} {snapshotDate} + </div> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/nav/component/component-nav.jsx b/server/sonar-web/src/main/js/apps/nav/component/component-nav.jsx new file mode 100644 index 00000000000..855482981d9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/nav/component/component-nav.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import ComponentNavFavorite from './component-nav-favorite'; +import ComponentNavBreadcrumbs from './component-nav-breadcrumbs'; +import ComponentNavMeta from './component-nav-meta'; +import ComponentNavMenu from './component-nav-menu'; + +let $ = jQuery; + +export default React.createClass({ + getInitialState() { + return { component: {}, conf: {} }; + }, + + componentDidMount() { + this.loadDetails(); + }, + + loadDetails() { + const url = `${window.baseUrl}/api/navigation/component`; + const data = { componentKey: this.props.componentKey }; + $.get(url, data).done(r => { + this.setState({ + component: r, + conf: r.configuration || {} + }); + }); + }, + + render() { + return ( + <div className="container"> + <ComponentNavFavorite + component={this.state.component.key} + favorite={this.state.component.isFavorite} + canBeFavorite={this.state.component.canBeFavorite}/> + + <ComponentNavBreadcrumbs + breadcrumbs={this.state.component.breadcrumbs}/> + + <ComponentNavMeta + version={this.state.component.version} + snapshotDate={this.state.component.snapshotDate}/> + + <ComponentNavMenu + component={this.state.component} + conf={this.state.conf}/> + </div> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/nav/dashboard-name-mixin.jsx b/server/sonar-web/src/main/js/apps/nav/dashboard-name-mixin.jsx new file mode 100644 index 00000000000..e8366f137c7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/nav/dashboard-name-mixin.jsx @@ -0,0 +1,11 @@ +export default { + getLocalizedDashboardName(baseName) { + var l10nKey = 'dashboard.' + baseName + '.name'; + var l10nLabel = window.t(l10nKey); + if (l10nLabel !== l10nKey) { + return l10nLabel; + } else { + return baseName; + } + } +}; diff --git a/server/sonar-web/src/main/js/apps/nav/global-nav-branding.jsx b/server/sonar-web/src/main/js/apps/nav/global/global-nav-branding.jsx index dccd40f4a36..dccd40f4a36 100644 --- a/server/sonar-web/src/main/js/apps/nav/global-nav-branding.jsx +++ b/server/sonar-web/src/main/js/apps/nav/global/global-nav-branding.jsx diff --git a/server/sonar-web/src/main/js/apps/nav/global-nav-menu.jsx b/server/sonar-web/src/main/js/apps/nav/global/global-nav-menu.jsx index 9aeb24baa77..037b0231210 100644 --- a/server/sonar-web/src/main/js/apps/nav/global-nav-menu.jsx +++ b/server/sonar-web/src/main/js/apps/nav/global/global-nav-menu.jsx @@ -1,6 +1,9 @@ import React from 'react'; +import DashboardNameMixin from '../dashboard-name-mixin'; export default React.createClass({ + mixins: [DashboardNameMixin], + getDefaultProps: function () { return { globalDashboards: [], globalPages: [] }; }, @@ -9,16 +12,6 @@ export default React.createClass({ return window.location.pathname.indexOf(window.baseUrl + url) === 0 ? 'active' : null; }, - getLocalizedDashboardName(baseName) { - var l10nKey = 'dashboard.' + baseName + '.name'; - var l10nLabel = window.t(l10nKey); - if (l10nLabel !== l10nKey) { - return l10nLabel; - } else { - return baseName; - } - }, - renderDashboardLink(dashboard) { const url = `${window.baseUrl}/dashboard/index?did=${encodeURIComponent(dashboard.key)}`; const name = this.getLocalizedDashboardName(dashboard.name); diff --git a/server/sonar-web/src/main/js/apps/nav/global-nav-search.jsx b/server/sonar-web/src/main/js/apps/nav/global/global-nav-search.jsx index 157ba7b25f7..157ba7b25f7 100644 --- a/server/sonar-web/src/main/js/apps/nav/global-nav-search.jsx +++ b/server/sonar-web/src/main/js/apps/nav/global/global-nav-search.jsx diff --git a/server/sonar-web/src/main/js/apps/nav/global-nav-user.jsx b/server/sonar-web/src/main/js/apps/nav/global/global-nav-user.jsx index d07c9a4d201..d07c9a4d201 100644 --- a/server/sonar-web/src/main/js/apps/nav/global-nav-user.jsx +++ b/server/sonar-web/src/main/js/apps/nav/global/global-nav-user.jsx diff --git a/server/sonar-web/src/main/js/apps/nav/global-nav.jsx b/server/sonar-web/src/main/js/apps/nav/global/global-nav.jsx index b7d8782268a..b7d8782268a 100644 --- a/server/sonar-web/src/main/js/apps/nav/global-nav.jsx +++ b/server/sonar-web/src/main/js/apps/nav/global/global-nav.jsx diff --git a/server/sonar-web/src/main/js/apps/nav/search-view.js b/server/sonar-web/src/main/js/apps/nav/global/search-view.js index d66a26508ff..233a1dc5f29 100644 --- a/server/sonar-web/src/main/js/apps/nav/search-view.js +++ b/server/sonar-web/src/main/js/apps/nav/global/search-view.js @@ -1,6 +1,6 @@ define([ 'components/common/selectable-collection-view', - './templates' + '../templates' ], function (SelectableCollectionView) { var $ = jQuery, diff --git a/server/sonar-web/src/main/js/apps/nav/shortcuts-help-view.js b/server/sonar-web/src/main/js/apps/nav/global/shortcuts-help-view.js index 39024617f7a..b016a734d8c 100644 --- a/server/sonar-web/src/main/js/apps/nav/shortcuts-help-view.js +++ b/server/sonar-web/src/main/js/apps/nav/global/shortcuts-help-view.js @@ -1,6 +1,6 @@ define([ 'components/common/modals', - './templates' + '../templates' ], function (ModalView) { return ModalView.extend({ diff --git a/server/sonar-web/src/main/js/components/shared/favorite.jsx b/server/sonar-web/src/main/js/components/shared/favorite.jsx new file mode 100644 index 00000000000..09601d31cd1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/shared/favorite.jsx @@ -0,0 +1,44 @@ +import React from 'react'; + +let $ = jQuery; + +export default React.createClass({ + propTypes: { + component: React.PropTypes.string.isRequired, + favorite: React.PropTypes.bool.isRequired + }, + + getInitialState() { + return { favorite: this.props.favorite }; + }, + + toggleFavorite(e) { + e.preventDefault(); + this.state.favorite ? this.removeFavorite() : this.addFavorite(); + }, + + addFavorite() { + const url = `${window.baseUrl}/api/favourites`; + const data = { key: this.props.component }; + $.ajax({ type: 'POST', url, data }).done(() => this.setState({ favorite: true })); + }, + + removeFavorite() { + const url = `${window.baseUrl}/api/favourites/${encodeURIComponent(this.props.component)}`; + $.ajax({ type: 'DELETE', url }).done(() => this.setState({ favorite: false })); + }, + + renderSVG() { + return ( + <svg width="16" height="16" style={{ fillRule: 'evenodd', clipRule: 'evenodd', strokeLinejoin: 'round', strokeMiterlimit: 1.41421 }}> + <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" + style={{ fillRule: 'nonzero' }}/> + </svg> + ) + }, + + render() { + const className = this.state.favorite ? 'icon-star icon-star-favorite' : 'icon-star'; + return <a onClick={this.toggleFavorite} className={className} href="#">{this.renderSVG()}</a>; + } +}); diff --git a/server/sonar-web/src/main/js/components/shared/qualifier-icon.jsx b/server/sonar-web/src/main/js/components/shared/qualifier-icon.jsx new file mode 100644 index 00000000000..e0f6e5a342d --- /dev/null +++ b/server/sonar-web/src/main/js/components/shared/qualifier-icon.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export default React.createClass({ + render() { + if (!this.props.qualifier) { + return null; + } + var className = 'icon-qualifier-' + this.props.qualifier.toLowerCase(); + return <i className={className}/>; + } +}); diff --git a/server/sonar-web/src/main/less/init/icons.less b/server/sonar-web/src/main/less/init/icons.less index f0d20455d5e..19d7e0cbaae 100644 --- a/server/sonar-web/src/main/less/init/icons.less +++ b/server/sonar-web/src/main/less/init/icons.less @@ -316,14 +316,47 @@ a[class^="icon-"], a[class*=" icon-"] { .square(16px); background-size: 16px 16px; background: no-repeat center center; + .trans !important; } .icon-favorite { background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M15.428%205.777c0%20.13-.078.274-.233.428l-3.24%203.16.767%204.465c.006.042.01.102.01.18%200%20.124-.032.23-.095.316-.062.086-.153.13-.272.13-.113%200-.232-.036-.357-.108l-4.01-2.107L3.99%2014.35c-.13.072-.25.107-.357.107-.125%200-.22-.043-.28-.13-.064-.085-.095-.19-.095-.316%200-.037.006-.096.018-.18l.768-4.464-3.25-3.16C.644%206.045.57%205.9.57%205.775c0-.22.167-.356.5-.41l4.482-.652L7.562.652c.112-.244.258-.366.437-.366.177%200%20.323.122.436.366l2.01%204.062%204.48.652c.335.054.5.19.5.41h.002z%22%20fill%3D%22%23F90%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E'); + .rotate(72deg); } .icon-not-favorite { background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2216%22%20height%3D%2216%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20stroke-linejoin%3D%22round%22%20stroke-miterlimit%3D%221.414%22%3E%3Cpath%20d%3D%22M15.428%205.777c0%20.13-.078.274-.233.428l-3.24%203.16.767%204.465c.006.042.01.102.01.18%200%20.124-.032.23-.095.316-.062.086-.153.13-.272.13-.113%200-.232-.036-.357-.108l-4.01-2.107L3.99%2014.35c-.13.072-.25.107-.357.107-.125%200-.22-.043-.28-.13-.064-.085-.095-.19-.095-.316%200-.037.006-.096.018-.18l.768-4.464-3.25-3.16C.644%206.045.57%205.9.57%205.775c0-.22.167-.356.5-.41l4.482-.652L7.562.652c.112-.244.258-.366.437-.366.177%200%20.323.122.436.366l2.01%204.062%204.48.652c.335.054.5.19.5.41h.002z%22%20fill%3D%22%23CDCDCD%22%20fill-rule%3D%22nonzero%22%2F%3E%3C%2Fsvg%3E'); } +.icon-star { + .trans !important; +} + +.icon-star path { + stroke: #777; + stroke-width: sqrt(2); + stroke-opacity: 1; + fill-opacity: 0; + .trans; +} + +.icon-star-favorite { + animation: spin .6s forwards; +} + +.icon-star-favorite path { + fill: rgb(255, 153, 0); + stroke-opacity: 0; + fill-opacity: 1; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(144deg); + } +} + .icon-help:before { content: "\f059"; color: @blue; diff --git a/server/sonar-web/test/intern.js b/server/sonar-web/test/intern.js index 74b2a6bd9ef..ee712c33f18 100644 --- a/server/sonar-web/test/intern.js +++ b/server/sonar-web/test/intern.js @@ -18,7 +18,8 @@ define(['intern'], function (intern) { 'test/unit/application.spec', 'test/unit/issue.spec', 'test/unit/overview/card.spec', - 'test/unit/code-with-issue-locations-helper.spec' + 'test/unit/code-with-issue-locations-helper.spec', + 'test/unit/nav/component/component-nav-breadcrumbs.spec' ], functionalSuites: [ @@ -40,7 +41,12 @@ define(['intern'], function (intern) { loaderOptions: { paths: { - 'react': 'build/js/libs/third-party/react-with-addons' + 'react': '../../build/js/libs/third-party/react-with-addons' + }, + map: { + '*': { + 'components/shared/qualifier-icon': '../../build/js/components/shared/qualifier-icon' + } } } }; diff --git a/server/sonar-web/test/unit/nav/component/component-nav-breadcrumbs.spec.js b/server/sonar-web/test/unit/nav/component/component-nav-breadcrumbs.spec.js new file mode 100644 index 00000000000..4b2a5fecaee --- /dev/null +++ b/server/sonar-web/test/unit/nav/component/component-nav-breadcrumbs.spec.js @@ -0,0 +1,27 @@ +define(function (require) { + var bdd = require('intern!bdd'); + var assert = require('intern/chai!assert'); + + var React = require('react'); + var TestUtils = React.addons.TestUtils; + + var ComponentNavBreadcrumbs = require('build/js/apps/nav/component/component-nav-breadcrumbs'); + + bdd.describe('ComponentNavBreadcrumbs', function () { + bdd.it('should not render unless `props.breadcrumbs`', function () { + var result = React.renderToStaticMarkup(React.createElement(ComponentNavBreadcrumbs, null)); + assert.equal(result, '<noscript></noscript>'); + }); + + bdd.it('should not render breadcrumbs with one element', function () { + var breadcrumbs = [ + { key: 'my-project', name: 'My Project', qualifier: 'TRK' } + ]; + var result = TestUtils.renderIntoDocument( + React.createElement(ComponentNavBreadcrumbs, { breadcrumbs: breadcrumbs }) + ); + assert.equal(TestUtils.scryRenderedDOMComponentsWithTag(result, 'li').length, 1); + assert.equal(TestUtils.scryRenderedDOMComponentsWithTag(result, 'a').length, 1); + }); + }); +}); |