* Display issues type icons instead of severity icons in codeviewer guttertags/7.8
@@ -123,7 +123,13 @@ export default class Line extends React.PureComponent<Props> { | |||
previousLine={this.props.previousLine} | |||
/> | |||
{this.props.displayCoverage && <LineCoverage line={line} />} | |||
{this.props.displayIssues && !this.props.displayAllIssues && ( | |||
<LineIssuesIndicator | |||
issues={this.props.issues} | |||
line={line} | |||
onClick={this.handleIssuesIndicatorClick} | |||
/> | |||
)} | |||
{this.props.displayDuplications && ( | |||
<LineDuplications line={line} onClick={this.props.loadDuplications} /> | |||
@@ -141,13 +147,7 @@ export default class Line extends React.PureComponent<Props> { | |||
/> | |||
))} | |||
{this.props.displayIssues && !this.props.displayAllIssues && ( | |||
<LineIssuesIndicator | |||
issues={this.props.issues} | |||
line={line} | |||
onClick={this.handleIssuesIndicatorClick} | |||
/> | |||
)} | |||
{this.props.displayCoverage && <LineCoverage line={line} />} | |||
<LineCode | |||
branchLike={this.props.branchLike} |
@@ -19,8 +19,8 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import SeverityIcon from '../../icons-components/SeverityIcon'; | |||
import { sortBySeverity } from '../../../helpers/issues'; | |||
import IssueIcon from '../../icons-components/IssueIcon'; | |||
import { sortByType } from '../../../helpers/issues'; | |||
interface Props { | |||
issues: T.Issue[]; | |||
@@ -40,7 +40,7 @@ export default class LineIssuesIndicator extends React.PureComponent<Props> { | |||
const className = classNames('source-meta', 'source-line-issues', { | |||
'source-line-with-issues': hasIssues | |||
}); | |||
const mostImportantIssue = hasIssues ? sortBySeverity(issues)[0] : null; | |||
const mostImportantIssue = hasIssues ? sortByType(issues)[0] : null; | |||
return ( | |||
<td | |||
@@ -49,7 +49,7 @@ export default class LineIssuesIndicator extends React.PureComponent<Props> { | |||
onClick={hasIssues ? this.handleClick : undefined} | |||
role={hasIssues ? 'button' : undefined} | |||
tabIndex={hasIssues ? 0 : undefined}> | |||
{mostImportantIssue != null && <SeverityIcon severity={mostImportantIssue.severity} />} | |||
{mostImportantIssue != null && <IssueIcon type={mostImportantIssue.type} />} | |||
{issues.length > 1 && <span className="source-line-issues-counter">{issues.length}</span>} | |||
</td> | |||
); |
@@ -0,0 +1,129 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Line from '../Line'; | |||
import { mockPullRequest, mockSourceLine, mockIssue } from '../../../../helpers/testMocks'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should render correctly for last, new, and highlighted lines', () => { | |||
expect( | |||
shallowRender({ | |||
highlighted: true, | |||
last: true, | |||
line: mockSourceLine({ isNew: true }) | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render correctly with coverage', () => { | |||
expect( | |||
shallowRender({ | |||
displayCoverage: true | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render correctly with duplication information', () => { | |||
expect( | |||
shallowRender({ | |||
displayDuplications: true, | |||
duplicationsCount: 3 | |||
}) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render correctly with issues info', () => { | |||
expect(shallowRender({ displayIssues: true })).toMatchSnapshot(); | |||
}); | |||
it('handles the opening and closing of issues', () => { | |||
const line = mockSourceLine(); | |||
const issue = mockIssue(); | |||
const onIssuesClose = jest.fn(); | |||
const onIssueUnselect = jest.fn(); | |||
const onIssuesOpen = jest.fn(); | |||
const onIssueSelect = jest.fn(); | |||
const wrapper = shallowRender({ | |||
issues: [issue], | |||
line, | |||
onIssuesClose, | |||
onIssueSelect, | |||
onIssuesOpen, | |||
onIssueUnselect, | |||
openIssues: true | |||
}); | |||
const instance = wrapper.instance(); | |||
instance.handleIssuesIndicatorClick(); | |||
expect(onIssuesClose).toBeCalledWith(line); | |||
expect(onIssueUnselect).toBeCalled(); | |||
wrapper.setProps({ openIssues: false }); | |||
instance.handleIssuesIndicatorClick(); | |||
expect(onIssuesOpen).toBeCalledWith(line); | |||
expect(onIssueSelect).toBeCalledWith(issue.key); | |||
}); | |||
function shallowRender(props: Partial<Line['props']> = {}) { | |||
return shallow<Line>( | |||
<Line | |||
branchLike={mockPullRequest()} | |||
displayAllIssues={false} | |||
displayCoverage={false} | |||
displayDuplications={false} | |||
displayIssueLocationsCount={false} | |||
displayIssueLocationsLink={false} | |||
displayIssues={false} | |||
displayLocationMarkers={false} | |||
duplications={[]} | |||
duplicationsCount={0} | |||
highlighted={false} | |||
highlightedLocationMessage={undefined} | |||
highlightedSymbols={undefined} | |||
issueLocations={[]} | |||
issuePopup={undefined} | |||
issues={[mockIssue(), mockIssue(false, { type: 'VULNERABILITY' })]} | |||
last={false} | |||
line={mockSourceLine()} | |||
linePopup={undefined} | |||
loadDuplications={jest.fn()} | |||
onLinePopupToggle={jest.fn()} | |||
onIssueChange={jest.fn()} | |||
onIssuePopupToggle={jest.fn()} | |||
onIssuesClose={jest.fn()} | |||
onIssueSelect={jest.fn()} | |||
onIssuesOpen={jest.fn()} | |||
onIssueUnselect={jest.fn()} | |||
onLocationSelect={jest.fn()} | |||
onSymbolClick={jest.fn()} | |||
openIssues={false} | |||
previousLine={undefined} | |||
renderDuplicationPopup={jest.fn()} | |||
scroll={jest.fn()} | |||
secondaryIssueLocations={[]} | |||
selectedIssue={undefined} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -19,56 +19,40 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import LineIssuesIndicator from '../LineIssuesIndicator'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import { mockIssue } from '../../../../helpers/testMocks'; | |||
const issueBase: T.Issue = { | |||
actions: [], | |||
component: '', | |||
componentLongName: '', | |||
componentQualifier: '', | |||
componentUuid: '', | |||
creationDate: '', | |||
key: '', | |||
flows: [], | |||
fromHotspot: false, | |||
message: '', | |||
organization: '', | |||
project: '', | |||
projectName: '', | |||
projectOrganization: '', | |||
projectKey: '', | |||
rule: '', | |||
ruleName: '', | |||
secondaryLocations: [], | |||
severity: '', | |||
status: '', | |||
transitions: [], | |||
type: 'BUG' | |||
}; | |||
it('render highest severity', () => { | |||
const line = { line: 3 }; | |||
const issues = [ | |||
{ ...issueBase, key: 'foo', severity: 'MINOR' }, | |||
{ ...issueBase, key: 'bar', severity: 'CRITICAL' } | |||
]; | |||
it('should render correctly', () => { | |||
const onClick = jest.fn(); | |||
const wrapper = shallow(<LineIssuesIndicator issues={issues} line={line} onClick={onClick} />); | |||
const wrapper = shallowRender({ onClick }); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper); | |||
expect(onClick).toHaveBeenCalled(); | |||
const nextIssues = [{ severity: 'MINOR' }, { severity: 'INFO' }]; | |||
const nextIssues = [ | |||
mockIssue(false, { key: 'foo', type: 'VULNERABILITY' }), | |||
mockIssue(false, { key: 'bar', type: 'SECURITY_HOTSPOT' }) | |||
]; | |||
wrapper.setProps({ issues: nextIssues }); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('no issues', () => { | |||
const line = { line: 3 }; | |||
const issues: T.Issue[] = []; | |||
const onClick = jest.fn(); | |||
const wrapper = shallow(<LineIssuesIndicator issues={issues} line={line} onClick={onClick} />); | |||
expect(wrapper).toMatchSnapshot(); | |||
it('should render correctly for no issues', () => { | |||
expect(shallowRender({ issues: [] })).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<LineIssuesIndicator['props']> = {}) { | |||
return shallow( | |||
<LineIssuesIndicator | |||
issues={[ | |||
mockIssue(false, { key: 'foo', type: 'CODE_SMELL' }), | |||
mockIssue(false, { key: 'bar', type: 'BUG' }) | |||
]} | |||
line={{ line: 3 }} | |||
onClick={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,800 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<tr | |||
className="source-line" | |||
data-line-number={5} | |||
> | |||
<LineNumber | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineSCM | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineCode | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
displayIssueLocationsCount={false} | |||
displayIssueLocationsLink={false} | |||
displayLocationMarkers={false} | |||
issueLocations={Array []} | |||
issues={ | |||
Array [ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
}, | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "VULNERABILITY", | |||
}, | |||
] | |||
} | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onIssueChange={[MockFunction]} | |||
onIssuePopupToggle={[MockFunction]} | |||
onIssueSelect={[MockFunction]} | |||
onLocationSelect={[MockFunction]} | |||
onSymbolClick={[MockFunction]} | |||
scroll={[MockFunction]} | |||
secondaryIssueLocations={Array []} | |||
showIssues={false} | |||
/> | |||
</tr> | |||
`; | |||
exports[`should render correctly for last, new, and highlighted lines 1`] = ` | |||
<tr | |||
className="source-line source-line-highlighted source-line-filtered source-line-last" | |||
data-line-number={5} | |||
> | |||
<LineNumber | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"isNew": true, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineSCM | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"isNew": true, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineCode | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
displayIssueLocationsCount={false} | |||
displayIssueLocationsLink={false} | |||
displayLocationMarkers={false} | |||
issueLocations={Array []} | |||
issues={ | |||
Array [ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
}, | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "VULNERABILITY", | |||
}, | |||
] | |||
} | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"isNew": true, | |||
"line": 5, | |||
} | |||
} | |||
onIssueChange={[MockFunction]} | |||
onIssuePopupToggle={[MockFunction]} | |||
onIssueSelect={[MockFunction]} | |||
onLocationSelect={[MockFunction]} | |||
onSymbolClick={[MockFunction]} | |||
scroll={[MockFunction]} | |||
secondaryIssueLocations={Array []} | |||
showIssues={false} | |||
/> | |||
</tr> | |||
`; | |||
exports[`should render correctly with coverage 1`] = ` | |||
<tr | |||
className="source-line" | |||
data-line-number={5} | |||
> | |||
<LineNumber | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineSCM | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineCoverage | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
/> | |||
<LineCode | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
displayIssueLocationsCount={false} | |||
displayIssueLocationsLink={false} | |||
displayLocationMarkers={false} | |||
issueLocations={Array []} | |||
issues={ | |||
Array [ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
}, | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "VULNERABILITY", | |||
}, | |||
] | |||
} | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onIssueChange={[MockFunction]} | |||
onIssuePopupToggle={[MockFunction]} | |||
onIssueSelect={[MockFunction]} | |||
onLocationSelect={[MockFunction]} | |||
onSymbolClick={[MockFunction]} | |||
scroll={[MockFunction]} | |||
secondaryIssueLocations={Array []} | |||
showIssues={false} | |||
/> | |||
</tr> | |||
`; | |||
exports[`should render correctly with duplication information 1`] = ` | |||
<tr | |||
className="source-line" | |||
data-line-number={5} | |||
> | |||
<LineNumber | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineSCM | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineDuplications | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onClick={[MockFunction]} | |||
/> | |||
<LineDuplicationBlock | |||
duplicated={false} | |||
index={0} | |||
key="0" | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
renderDuplicationPopup={[MockFunction]} | |||
/> | |||
<LineDuplicationBlock | |||
duplicated={false} | |||
index={1} | |||
key="1" | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
renderDuplicationPopup={[MockFunction]} | |||
/> | |||
<LineDuplicationBlock | |||
duplicated={false} | |||
index={2} | |||
key="2" | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
renderDuplicationPopup={[MockFunction]} | |||
/> | |||
<LineCode | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
displayIssueLocationsCount={false} | |||
displayIssueLocationsLink={false} | |||
displayLocationMarkers={false} | |||
issueLocations={Array []} | |||
issues={ | |||
Array [ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
}, | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "VULNERABILITY", | |||
}, | |||
] | |||
} | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onIssueChange={[MockFunction]} | |||
onIssuePopupToggle={[MockFunction]} | |||
onIssueSelect={[MockFunction]} | |||
onLocationSelect={[MockFunction]} | |||
onSymbolClick={[MockFunction]} | |||
scroll={[MockFunction]} | |||
secondaryIssueLocations={Array []} | |||
showIssues={false} | |||
/> | |||
</tr> | |||
`; | |||
exports[`should render correctly with issues info 1`] = ` | |||
<tr | |||
className="source-line" | |||
data-line-number={5} | |||
> | |||
<LineNumber | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineSCM | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onPopupToggle={[MockFunction]} | |||
popupOpen={false} | |||
/> | |||
<LineIssuesIndicator | |||
issues={ | |||
Array [ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
}, | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "VULNERABILITY", | |||
}, | |||
] | |||
} | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onClick={[Function]} | |||
/> | |||
<LineCode | |||
branchLike={ | |||
Object { | |||
"analysisDate": "2018-01-01", | |||
"base": "master", | |||
"branch": "feature/foo/bar", | |||
"key": "1001", | |||
"target": "master", | |||
"title": "Foo Bar feature", | |||
} | |||
} | |||
displayIssueLocationsCount={false} | |||
displayIssueLocationsLink={false} | |||
displayLocationMarkers={false} | |||
issueLocations={Array []} | |||
issues={ | |||
Array [ | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "BUG", | |||
}, | |||
Object { | |||
"actions": Array [], | |||
"component": "main.js", | |||
"componentLongName": "main.js", | |||
"componentQualifier": "FIL", | |||
"componentUuid": "foo1234", | |||
"creationDate": "2017-03-01T09:36:01+0100", | |||
"flows": Array [], | |||
"fromHotspot": false, | |||
"key": "AVsae-CQS-9G3txfbFN2", | |||
"line": 25, | |||
"message": "Reduce the number of conditional operators (4) used in the expression", | |||
"organization": "myorg", | |||
"project": "myproject", | |||
"projectKey": "foo", | |||
"projectName": "Foo", | |||
"projectOrganization": "org", | |||
"rule": "javascript:S1067", | |||
"ruleName": "foo", | |||
"secondaryLocations": Array [], | |||
"severity": "MAJOR", | |||
"status": "OPEN", | |||
"textRange": Object { | |||
"endLine": 26, | |||
"endOffset": 15, | |||
"startLine": 25, | |||
"startOffset": 0, | |||
}, | |||
"transitions": Array [], | |||
"type": "VULNERABILITY", | |||
}, | |||
] | |||
} | |||
line={ | |||
Object { | |||
"code": "function fooBar() {", | |||
"coverageStatus": "covered", | |||
"coveredConditions": 2, | |||
"line": 5, | |||
} | |||
} | |||
onIssueChange={[MockFunction]} | |||
onIssuePopupToggle={[MockFunction]} | |||
onIssueSelect={[MockFunction]} | |||
onLocationSelect={[MockFunction]} | |||
onSymbolClick={[MockFunction]} | |||
scroll={[MockFunction]} | |||
secondaryIssueLocations={Array []} | |||
showIssues={false} | |||
/> | |||
</tr> | |||
`; |
@@ -1,13 +1,6 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`no issues 1`] = ` | |||
<td | |||
className="source-meta source-line-issues" | |||
data-line-number={3} | |||
/> | |||
`; | |||
exports[`render highest severity 1`] = ` | |||
exports[`should render correctly 1`] = ` | |||
<td | |||
className="source-meta source-line-issues source-line-with-issues" | |||
data-line-number={3} | |||
@@ -15,8 +8,8 @@ exports[`render highest severity 1`] = ` | |||
role="button" | |||
tabIndex={0} | |||
> | |||
<SeverityIcon | |||
severity="CRITICAL" | |||
<IssueIcon | |||
type="BUG" | |||
/> | |||
<span | |||
className="source-line-issues-counter" | |||
@@ -26,7 +19,7 @@ exports[`render highest severity 1`] = ` | |||
</td> | |||
`; | |||
exports[`render highest severity 2`] = ` | |||
exports[`should render correctly 2`] = ` | |||
<td | |||
className="source-meta source-line-issues source-line-with-issues" | |||
data-line-number={3} | |||
@@ -34,8 +27,8 @@ exports[`render highest severity 2`] = ` | |||
role="button" | |||
tabIndex={0} | |||
> | |||
<SeverityIcon | |||
severity="MINOR" | |||
<IssueIcon | |||
type="VULNERABILITY" | |||
/> | |||
<span | |||
className="source-line-issues-counter" | |||
@@ -44,3 +37,10 @@ exports[`render highest severity 2`] = ` | |||
</span> | |||
</td> | |||
`; | |||
exports[`should render correctly for no issues 1`] = ` | |||
<td | |||
className="source-meta source-line-issues" | |||
data-line-number={3} | |||
/> | |||
`; |
@@ -174,14 +174,19 @@ | |||
position: relative; | |||
padding: 0 2px; | |||
background-color: var(--barBackgroundColor); | |||
white-space: nowrap; | |||
} | |||
.source-line-with-issues { | |||
padding-right: 4px; | |||
} | |||
.source-line-issues-counter { | |||
position: absolute; | |||
top: -1px; | |||
right: -1px; | |||
left: 17px; | |||
line-height: 8px; | |||
font-size: 8px; | |||
z-index: 900; | |||
} | |||
.source-line-coverage { |
@@ -0,0 +1,45 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import BugIcon from '../icons-components/BugIcon'; | |||
import VulnerabilityIcon from '../icons-components/VulnerabilityIcon'; | |||
import CodeSmellIcon from '../icons-components/CodeSmellIcon'; | |||
import SecurityHotspotIcon from '../icons-components/SecurityHotspotIcon'; | |||
interface Props { | |||
className?: string; | |||
type: T.IssueType; | |||
size?: number; | |||
} | |||
export default function IssueIcon({ className, type, size }: Props) { | |||
switch (type) { | |||
case 'BUG': | |||
return <BugIcon className={className} size={size} />; | |||
case 'VULNERABILITY': | |||
return <VulnerabilityIcon className={className} size={size} />; | |||
case 'CODE_SMELL': | |||
return <CodeSmellIcon className={className} size={size} />; | |||
case 'SECURITY_HOTSPOT': | |||
return <SecurityHotspotIcon className={className} size={size} />; | |||
default: | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import IssueIcon from '../IssueIcon'; | |||
it('should render correctly', () => { | |||
expect(shallowRender('BUG')).toMatchSnapshot(); | |||
expect(shallowRender('VULNERABILITY')).toMatchSnapshot(); | |||
expect(shallowRender('CODE_SMELL')).toMatchSnapshot(); | |||
expect(shallowRender('SECURITY_HOTSPOT')).toMatchSnapshot(); | |||
}); | |||
function shallowRender(type: T.IssueType) { | |||
return shallow(<IssueIcon type={type} />); | |||
} |
@@ -0,0 +1,9 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = `<BugIcon />`; | |||
exports[`should render correctly 2`] = `<VulnerabilityIcon />`; | |||
exports[`should render correctly 3`] = `<CodeSmellIcon />`; | |||
exports[`should render correctly 4`] = `<SecurityHotspotIcon />`; |
@@ -18,45 +18,41 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import BugIcon from '../icons-components/BugIcon'; | |||
import VulnerabilityIcon from '../icons-components/VulnerabilityIcon'; | |||
import CodeSmellIcon from '../icons-components/CodeSmellIcon'; | |||
import SecurityHotspotIcon from '../icons-components/SecurityHotspotIcon'; | |||
import IssueIcon from '../icons-components/IssueIcon'; | |||
interface Props { | |||
export interface Props { | |||
className?: string; | |||
query: string; | |||
size?: number; | |||
} | |||
export default function IssueTypeIcon({ className, query, size }: Props) { | |||
let icon; | |||
let type: T.IssueType; | |||
switch (query.toLowerCase()) { | |||
case 'bug': | |||
case 'bugs': | |||
case 'new_bugs': | |||
icon = <BugIcon size={size} />; | |||
type = 'BUG'; | |||
break; | |||
case 'vulnerability': | |||
case 'vulnerabilities': | |||
case 'new_vulnerabilities': | |||
icon = <VulnerabilityIcon size={size} />; | |||
type = 'VULNERABILITY'; | |||
break; | |||
case 'code_smell': | |||
case 'code_smells': | |||
case 'new_code_smells': | |||
icon = <CodeSmellIcon size={size} />; | |||
type = 'CODE_SMELL'; | |||
break; | |||
case 'security_hotspot': | |||
case 'security_hotspots': | |||
icon = <SecurityHotspotIcon size={size} />; | |||
type = 'SECURITY_HOTSPOT'; | |||
break; | |||
default: | |||
return null; | |||
} | |||
if (!icon) { | |||
return null; | |||
} | |||
const icon = <IssueIcon size={size} type={type} />; | |||
return className ? <span className={className}>{icon}</span> : icon; | |||
} |
@@ -0,0 +1,34 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import IssueTypeIcon, { Props } from '../IssueTypeIcon'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ className: 'my-class', query: 'security_hotspots' })).toMatchSnapshot(); | |||
expect(shallowRender({ query: 'new_code_smells' })).toMatchSnapshot(); | |||
expect(shallowRender({ query: 'vulnerability' })).toMatchSnapshot(); | |||
expect(shallowRender({ query: 'unknown' }).type()).toBe(null); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
return shallow(<IssueTypeIcon query="bugs" {...props} />); | |||
} |
@@ -0,0 +1,29 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<IssueIcon | |||
type="BUG" | |||
/> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<span | |||
className="my-class" | |||
> | |||
<IssueIcon | |||
type="SECURITY_HOTSPOT" | |||
/> | |||
</span> | |||
`; | |||
exports[`should render correctly 3`] = ` | |||
<IssueIcon | |||
type="CODE_SMELL" | |||
/> | |||
`; | |||
exports[`should render correctly 4`] = ` | |||
<IssueIcon | |||
type="VULNERABILITY" | |||
/> | |||
`; |
@@ -17,7 +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 { parseIssueFromResponse } from '../issues'; | |||
import { parseIssueFromResponse, sortByType } from '../issues'; | |||
import { mockIssue } from '../testMocks'; | |||
it('should sort issues correctly by type', () => { | |||
const bug1 = mockIssue(false, { type: 'BUG', key: 'bug1' }); | |||
const bug2 = mockIssue(false, { type: 'BUG', key: 'bug2' }); | |||
const codeSmell = mockIssue(false, { type: 'CODE_SMELL', key: 'code_smell' }); | |||
const vulnerability1 = mockIssue(false, { type: 'VULNERABILITY', key: 'vulnerability1' }); | |||
const vulnerability2 = mockIssue(false, { type: 'VULNERABILITY', key: 'vulnerability2' }); | |||
const securityHotspot = mockIssue(false, { type: 'SECURITY_HOTSPOT', key: 'security_hotspot' }); | |||
expect( | |||
sortByType([bug1, codeSmell, bug2, securityHotspot, vulnerability1, vulnerability2]) | |||
).toEqual([bug1, bug2, vulnerability1, vulnerability2, codeSmell, securityHotspot]); | |||
}); | |||
it('should populate comments data', () => { | |||
const users = [ |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { flatten, sortBy } from 'lodash'; | |||
import { SEVERITIES } from './constants'; | |||
import { ISSUE_TYPES } from './constants'; | |||
interface Comment { | |||
login: string; | |||
@@ -59,8 +59,8 @@ export interface RawIssue extends IssueBase { | |||
textRange?: T.TextRange; | |||
} | |||
export function sortBySeverity(issues: T.Issue[]): T.Issue[] { | |||
return sortBy(issues, issue => SEVERITIES.indexOf(issue.severity)); | |||
export function sortByType(issues: T.Issue[]): T.Issue[] { | |||
return sortBy(issues, issue => ISSUE_TYPES.indexOf(issue.type)); | |||
} | |||
function injectRelational( |
@@ -477,6 +477,16 @@ export function mockStore(state: any = {}, reducer = (state: any) => state): Sto | |||
return createStore(reducer, state); | |||
} | |||
export function mockSourceLine(overrides: Partial<T.SourceLine> = {}): T.SourceLine { | |||
return { | |||
code: 'function fooBar() {', | |||
coverageStatus: 'covered', | |||
coveredConditions: 2, | |||
line: 5, | |||
...overrides | |||
}; | |||
} | |||
export function mockDocumentationEntry( | |||
overrides: Partial<DocumentationEntry> = {} | |||
): DocumentationEntry { |