handleIssueChange = (issue: T.Issue) => {
this.refreshBranchStatus();
this.setState(state => ({
- issues: state.issues.map(candidate => (candidate.key === issue.key ? issue : candidate))
+ issues: state.issues.map(candidate => (candidate.key === issue.key ? issue : candidate)),
+ openIssue: state.openIssue && state.openIssue.key === issue.key ? issue : state.openIssue
}));
};
const updatedIssue: T.Issue = { ...ISSUES[0], type: 'SECURITY_HOTSPOT' };
instance.handleIssueChange(updatedIssue);
- expect(wrapper.state('issues')).toEqual([updatedIssue, ISSUES[1], ISSUES[2], ISSUES[3]]);
+ expect(wrapper.state().issues).toEqual([updatedIssue, ISSUES[1], ISSUES[2], ISSUES[3]]);
expect(fetchBranchStatus).toBeCalledWith(branchLike, component.key);
fetchBranchStatus.mockClear();
expect(fetchBranchStatus).toBeCalled();
});
+it('should update the open issue when it is changed', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+
+ wrapper.setState({ openIssue: ISSUES[0] });
+
+ const updatedIssue: T.Issue = { ...ISSUES[0], type: 'SECURITY_HOTSPOT' };
+ wrapper.instance().handleIssueChange(updatedIssue);
+
+ expect(wrapper.state().openIssue).toBe(updatedIssue);
+});
+
it('should handle createAfter query param with time', async () => {
const fetchIssues = fetchIssuesMockFactory();
const wrapper = shallowRender({
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { noop } from 'lodash';
import * as React from 'react';
import { getSources } from '../../../api/components';
+import Issue from '../../../components/issue/Issue';
import getCoverageStatus from '../../../components/SourceViewer/helpers/getCoverageStatus';
import { locationsByLine } from '../../../components/SourceViewer/helpers/indexing';
import SourceViewerHeaderSlim from '../../../components/SourceViewer/SourceViewerHeaderSlim';
}
render() {
- const { branchLike, issue, issuesByLine, lastSnippetGroup, snippetGroup } = this.props;
+ const {
+ branchLike,
+ issue,
+ issuesByLine,
+ issuePopup,
+ lastSnippetGroup,
+ snippetGroup
+ } = this.props;
const { additionalLines, loading, snippets } = this.state;
const locations =
- issue.component === snippetGroup.component.key ? locationsByLine([issue]) : {};
+ issue.component === snippetGroup.component.key && issue.textRange !== undefined
+ ? locationsByLine([issue])
+ : {};
const fullyShown =
snippets.length === 1 &&
onExpand={this.expandComponent}
sourceViewerFile={snippetGroup.component}
/>
+ {issue.component === snippetGroup.component.key && issue.textRange === undefined && (
+ <div className="padded-top padded-left padded-right">
+ <Issue
+ issue={issue}
+ onChange={this.props.onIssueChange}
+ onClick={noop}
+ onPopupToggle={this.props.onIssuePopupToggle}
+ openPopup={issuePopup && issuePopup.issue === issue.key ? issuePopup.name : undefined}
+ selected={true}
+ />
+ </div>
+ )}
{snippetLines.map((snippet, index) => (
<div id={`snippet-wrapper-${snippets[index].index}`} key={snippets[index].index}>
{this.renderSnippet({
import * as React from 'react';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getSources } from '../../../../api/components';
+import Issue from '../../../../components/issue/Issue';
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
import {
mockFlowLocation,
expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 69, end: 79 });
});
+it('should render file-level issue correctly', () => {
+ // issue with secondary locations and no primary location
+ const issue = mockIssue(true, {
+ flows: [],
+ textRange: undefined
+ });
+
+ const wrapper = shallowRender({
+ issue,
+ snippetGroup: {
+ locations: [
+ mockFlowLocation({
+ component: issue.component,
+ textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
+ })
+ ],
+ ...mockSnippetsByComponent(issue.component, range(29, 39))
+ }
+ });
+
+ expect(wrapper.find(Issue).exists()).toBe(true);
+});
+
it('should expand block', async () => {
(getSources as jest.Mock).mockResolvedValueOnce(
Object.values(mockSnippetsByComponent('a', range(6, 59)).sources)
expect(results[1]).toEqual({ index: 2, start: 37, end: 47 });
});
- it('should work for location with no textrange', () => {
+ it('should ignore location with no textrange', () => {
const locations = [
mockFlowLocation({
textRange: { startLine: 85, startOffset: 2, endLine: 85, endOffset: 3 }
issue
});
- expect(results).toHaveLength(2);
- expect(results[0]).toEqual({ index: 0, start: 1, end: 9 });
+ expect(results).toHaveLength(1);
+ expect(results[0]).toEqual({ index: 0, start: 80, end: 94 });
});
});
const { component, issue, locations } = params;
const hasSecondaryLocations = issue.secondaryLocations.length > 0;
- const addIssueLocation = hasSecondaryLocations && issue.component === component;
+ const addIssueLocation =
+ hasSecondaryLocations && issue.component === component && issue.textRange !== undefined;
// For each location: compute its range, and then compare with other ranges
// to merge snippets that collide.
let currentGroup: T.SnippetGroup;
const groups: T.SnippetGroup[] = [];
- const addGroup = (loc: T.FlowLocation) => {
+ const addGroup = (componentKey: string) => {
currentGroup = {
- ...(components[loc.component] || unknownComponent(loc.component)),
+ ...(components[componentKey] || unknownComponent(componentKey)),
locations: []
};
groups.push(currentGroup);
- currentComponent = loc.component;
+ currentComponent = componentKey;
};
if (
issue.secondaryLocations.length > 0 &&
locations.every(loc => loc.component !== issue.component)
) {
- addGroup(getPrimaryLocation(issue));
+ addGroup(issue.component);
}
locations.forEach((loc, index) => {
if (loc.component !== currentComponent) {
- addGroup(loc);
+ addGroup(loc.component);
}
loc.index = index;
currentGroup.locations.push(loc);