diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2022-02-22 09:29:32 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-02-25 20:02:54 +0000 |
commit | 50fa615acd453be581adda89e7fec784d3adcc34 (patch) | |
tree | d73debfa8a96bcaffaa0219f0816eb616443d8b9 | |
parent | 1a4de4eb41d7c9a62476ff43e8360960da74dcf1 (diff) | |
download | sonarqube-50fa615acd453be581adda89e7fec784d3adcc34.tar.gz sonarqube-50fa615acd453be581adda89e7fec784d3adcc34.zip |
SONAR-16007 Binding secondary location click in snippet and hotspot box
36 files changed, 444 insertions, 25 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx index 2411146ab9c..4cadb1885df 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx @@ -118,7 +118,6 @@ export default class ConciseIssueBox extends React.PureComponent<Props> { isCrossFile={isCrossFile} onLocationSelect={this.props.onLocationSelect} scroll={this.props.scroll} - selectedFlowIndex={selectedFlowIndex} selectedLocationIndex={selectedLocationIndex} /> )} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap index 5badb12ee28..a3239afee18 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap @@ -60,7 +60,6 @@ exports[`should render correctly 1`] = ` locations={Array []} onLocationSelect={[MockFunction]} scroll={[MockFunction]} - selectedFlowIndex={0} selectedLocationIndex={0} uniqueKey="AVsae-CQS-9G3txfbFN2" /> @@ -226,7 +225,6 @@ exports[`should render correctly 2`] = ` } onLocationSelect={[MockFunction]} scroll={[MockFunction]} - selectedFlowIndex={0} selectedLocationIndex={0} uniqueKey="AVsae-CQS-9G3txfbFN2" /> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx index f90fc16bf4b..975dd3c674f 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx @@ -28,6 +28,7 @@ import { withCurrentUser } from '../../components/hoc/withCurrentUser'; import { Router } from '../../components/hoc/withRouter'; import { getLeakValue } from '../../components/measure/utils'; import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpers/branch-like'; +import { scrollToElement } from '../../helpers/scrolling'; import { getStandards } from '../../helpers/security-standard'; import { isLoggedIn } from '../../helpers/users'; import { fetchBranchStatus } from '../../store/rootActions'; @@ -75,7 +76,7 @@ interface State { loadingMeasure: boolean; loadingMore: boolean; selectedHotspot?: RawHotspot; - selectedHotspotLocation?: number; + selectedHotspotLocationIndex?: number; standards: Standards; } @@ -353,7 +354,8 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { this.handleChangeFilters({ status }); }; - handleHotspotClick = (selectedHotspot: RawHotspot) => this.setState({ selectedHotspot }); + handleHotspotClick = (selectedHotspot: RawHotspot) => + this.setState({ selectedHotspot, selectedHotspotLocationIndex: undefined }); handleHotspotUpdate = (hotspotKey: string) => { const { hotspots, hotspotsPageIndex } = this.state; @@ -419,6 +421,26 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { .catch(this.handleCallFailure); }; + handleLocationClick = (locationIndex: number) => { + const { selectedHotspotLocationIndex } = this.state; + if (locationIndex === selectedHotspotLocationIndex) { + this.setState({ + selectedHotspotLocationIndex: undefined + }); + } else { + this.setState({ + selectedHotspotLocationIndex: locationIndex + }); + } + }; + + handleScroll = (element: Element, bottomOffset = 100) => { + const scrollableElement = document.querySelector('.layout-page-side'); + if (element && scrollableElement) { + scrollToElement(element, { topOffset: 150, bottomOffset, parent: scrollableElement }); + } + }; + render() { const { branchLike, component } = this.props; const { @@ -434,6 +456,7 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { loadingMeasure, loadingMore, selectedHotspot, + selectedHotspotLocationIndex, standards } = this.state; @@ -460,8 +483,11 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> { onShowAllHotspots={this.handleShowAllHotspots} onSwitchStatusFilter={this.handleChangeStatusFilter} onUpdateHotspot={this.handleHotspotUpdate} + onLocationClick={this.handleLocationClick} + onScroll={this.handleScroll} securityCategories={standards[SecurityStandard.SONARSOURCE]} selectedHotspot={selectedHotspot} + selectedHotspotLocation={selectedHotspotLocationIndex} standards={standards} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx index deaf72093cd..4de97e96492 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsAppRenderer.tsx @@ -56,10 +56,12 @@ export interface SecurityHotspotsAppRendererProps { loadingMore: boolean; onChangeFilters: (filters: Partial<HotspotFilters>) => void; onHotspotClick: (hotspot: RawHotspot) => void; + onLocationClick: (index: number) => void; onLoadMore: () => void; onShowAllHotspots: () => void; onSwitchStatusFilter: (option: HotspotStatusFilter) => void; onUpdateHotspot: (hotspotKey: string) => Promise<void>; + onScroll: (element: Element) => void; selectedHotspot?: RawHotspot; selectedHotspotLocation?: number; securityCategories: StandardSecurityCategories; @@ -83,6 +85,7 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe loadingMore, securityCategories, selectedHotspot, + selectedHotspotLocation, standards } = props; @@ -149,6 +152,9 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe loadingMore={loadingMore} onHotspotClick={props.onHotspotClick} onLoadMore={props.onLoadMore} + onLocationClick={props.onLocationClick} + onScroll={props.onScroll} + selectedHotspotLocation={selectedHotspotLocation} selectedHotspot={selectedHotspot} standards={standards} /> @@ -160,9 +166,12 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe loadingMore={loadingMore} onHotspotClick={props.onHotspotClick} onLoadMore={props.onLoadMore} + onLocationClick={props.onLocationClick} securityCategories={securityCategories} selectedHotspot={selectedHotspot} + selectedHotspotLocation={selectedHotspotLocation} statusFilter={filters.status} + onScroll={props.onScroll} /> )} </div> @@ -177,6 +186,8 @@ export default function SecurityHotspotsAppRenderer(props: SecurityHotspotsAppRe hotspotsReviewedMeasure={hotspotsReviewedMeasure} onSwitchStatusFilter={props.onSwitchStatusFilter} onUpdateHotspot={props.onUpdateHotspot} + onLocationClick={props.onLocationClick} + selectedHotspotLocation={selectedHotspotLocation} /> </div> </div> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx index 5da406c5330..ed73077f227 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx @@ -19,6 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockHtmlElement } from '../../../helpers/mocks/dom'; import { getMeasures } from '../../../api/measures'; import { getSecurityHotspotList, getSecurityHotspots } from '../../../api/security-hotspots'; import { mockBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; @@ -40,6 +41,7 @@ import { } from '../../../types/security-hotspots'; import { SecurityHotspotsApp } from '../SecurityHotspotsApp'; import SecurityHotspotsAppRenderer from '../SecurityHotspotsAppRenderer'; +import { scrollToElement } from '../../../helpers/scrolling'; beforeEach(() => jest.clearAllMocks()); @@ -56,6 +58,10 @@ jest.mock('../../../helpers/security-standard', () => ({ getStandards: jest.fn().mockResolvedValue({ sonarsourceSecurity: { cat1: { title: 'cat 1' } } }) })); +jest.mock('../../../helpers/scrolling', () => ({ + scrollToElement: jest.fn() +})); + const branch = mockBranch(); it('should render correctly', () => { @@ -379,6 +385,41 @@ it('should handle leakPeriod filter change', async () => { expect(getSecurityHotspots).toBeCalledWith(expect.objectContaining({ sinceLeakPeriod: true })); }); +it('should handle hotspot click', () => { + const wrapper = shallowRender(); + const selectedHotspot = mockRawHotspot(); + wrapper.instance().handleHotspotClick(selectedHotspot); + + expect(wrapper.instance().state.selectedHotspotLocationIndex).toBeUndefined(); + expect(wrapper.instance().state.selectedHotspot).toEqual(selectedHotspot); +}); + +it('should handle secondary location click', () => { + const wrapper = shallowRender(); + wrapper.instance().handleLocationClick(0); + expect(wrapper.instance().state.selectedHotspotLocationIndex).toEqual(0); + + wrapper.instance().handleLocationClick(1); + expect(wrapper.instance().state.selectedHotspotLocationIndex).toEqual(1); + + wrapper.instance().handleLocationClick(1); + expect(wrapper.instance().state.selectedHotspotLocationIndex).toBeUndefined(); +}); + +it('should handle scroll properly', async () => { + const fakeElement = document.createElement('div'); + jest.spyOn(document, 'querySelector').mockImplementationOnce(() => fakeElement); + const wrapper = shallowRender(); + const element = mockHtmlElement(); + wrapper.instance().handleScroll(element); + await waitAndUpdate(wrapper); + expect(scrollToElement).toBeCalledWith(element, { + bottomOffset: 100, + parent: fakeElement, + topOffset: 150 + }); +}); + describe('keyboard navigation', () => { const hotspots = [ mockRawHotspot({ key: 'k1' }), diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx index 577ed513560..6f1b477c1c1 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx @@ -146,6 +146,8 @@ function shallowRender(props: Partial<SecurityHotspotsAppRendererProps> = {}) { onShowAllHotspots={jest.fn()} onSwitchStatusFilter={jest.fn()} onUpdateHotspot={jest.fn()} + onLocationClick={jest.fn()} + onScroll={jest.fn()} securityCategories={{}} selectedHotspot={undefined} standards={mockStandards()} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap index 4775fd8a595..1762e1a557e 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap @@ -48,6 +48,8 @@ exports[`should render correctly 1`] = ` onChangeFilters={[Function]} onHotspotClick={[Function]} onLoadMore={[Function]} + onLocationClick={[Function]} + onScroll={[Function]} onShowAllHotspots={[Function]} onSwitchStatusFilter={[Function]} onUpdateHotspot={[Function]} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap index ce093d196af..bd4730c4110 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap @@ -117,6 +117,8 @@ exports[`should render correctly when filtered by category or cwe: category 1`] loadingMore={false} onHotspotClick={[MockFunction]} onLoadMore={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selectedHotspot={ Object { "author": "Developer 1", @@ -236,6 +238,8 @@ exports[`should render correctly when filtered by category or cwe: cwe 1`] = ` loadingMore={false} onHotspotClick={[MockFunction]} onLoadMore={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selectedHotspot={ Object { "author": "Developer 1", @@ -415,6 +419,8 @@ exports[`should render correctly with hotspots 2`] = ` loadingMore={false} onHotspotClick={[MockFunction]} onLoadMore={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} securityCategories={Object {}} selectedHotspot={ Object { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx index fa176838b89..5365ef9d372 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotCategory.tsx @@ -30,6 +30,8 @@ export interface HotspotCategoryProps { hotspots: RawHotspot[]; onHotspotClick: (hotspot: RawHotspot) => void; onToggleExpand?: (categoryKey: string, value: boolean) => void; + onLocationClick: (index: number) => void; + onScroll: (element: Element) => void; selectedHotspot: RawHotspot; selectedHotspotLocation?: number; title: string; @@ -37,7 +39,15 @@ export interface HotspotCategoryProps { } export default function HotspotCategory(props: HotspotCategoryProps) { - const { categoryKey, expanded, hotspots, selectedHotspot, title, isLastAndIncomplete } = props; + const { + categoryKey, + expanded, + hotspots, + selectedHotspot, + title, + isLastAndIncomplete, + selectedHotspotLocation + } = props; if (hotspots.length < 1) { return null; @@ -80,6 +90,9 @@ export default function HotspotCategory(props: HotspotCategoryProps) { <HotspotListItem hotspot={h} onClick={props.onHotspotClick} + onLocationClick={props.onLocationClick} + onScroll={props.onScroll} + selectedHotspotLocation={selectedHotspotLocation} selected={h.key === selectedHotspot.key} /> </li> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx index afa5194f180..610aa769544 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotList.tsx @@ -37,6 +37,8 @@ interface Props { loadingMore: boolean; onHotspotClick: (hotspot: RawHotspot) => void; onLoadMore: () => void; + onLocationClick: (index: number) => void; + onScroll: (element: Element) => void; securityCategories: StandardSecurityCategories; selectedHotspot: RawHotspot; selectedHotspotLocation?: number; @@ -84,6 +86,13 @@ export default class HotspotList extends React.Component<Props, State> { ); this.setState({ groupedHotspots }); } + + if (this.props.selectedHotspotLocation !== prevProps.selectedHotspotLocation) { + const { selectedHotspot } = this.props; + this.setState({ + expandedCategories: { [selectedHotspot.securityCategory]: true } + }); + } } componentWillUnmount() { @@ -112,6 +121,7 @@ export default class HotspotList extends React.Component<Props, State> { isStaticListOfHotspots, loadingMore, selectedHotspot, + selectedHotspotLocation, statusFilter } = this.props; @@ -150,7 +160,10 @@ export default class HotspotList extends React.Component<Props, State> { hotspots={cat.hotspots} onHotspotClick={this.props.onHotspotClick} onToggleExpand={this.handleToggleCategory} + onLocationClick={this.props.onLocationClick} + onScroll={this.props.onScroll} selectedHotspot={selectedHotspot} + selectedHotspotLocation={selectedHotspotLocation} title={cat.title} isLastAndIncomplete={ isLastRiskGroup && isLastCategory && hotspots.length < hotspotsTotal diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx index 7865080337c..e2e34da722e 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotListItem.tsx @@ -22,17 +22,20 @@ import * as React from 'react'; import QualifierIcon from '../../../components/icons/QualifierIcon'; import { ComponentQualifier } from '../../../types/component'; import { getFilePath, getLocations } from '../utils'; -import LocationsList from '../../../components/locations/LocationsList'; import { RawHotspot } from '../../../types/security-hotspots'; +import LocationsList from '../../../components/locations/LocationsList'; export interface HotspotListItemProps { hotspot: RawHotspot; onClick: (hotspot: RawHotspot) => void; + onLocationClick: (index: number) => void; + onScroll: (element: Element) => void; selected: boolean; + selectedHotspotLocation?: number; } export default function HotspotListItem(props: HotspotListItemProps) { - const { hotspot, selected } = props; + const { hotspot, selected, selectedHotspotLocation } = props; const locations = getLocations(hotspot.flows, undefined); const path = getFilePath(hotspot.component, hotspot.project); @@ -56,12 +59,9 @@ export default function HotspotListItem(props: HotspotListItemProps) { locations={locations} isCrossFile={false} // Currently we are not supporting cross file for security hotspot uniqueKey={hotspot.key} - onLocationSelect={() => { - /* noop */ - }} - scroll={() => { - /* noop */ - }} + onLocationSelect={props.onLocationClick} + selectedLocationIndex={selectedHotspotLocation} + scroll={props.onScroll} /> )} </div> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx index 11a96cbccc4..ca7c81cd7a3 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSimpleList.tsx @@ -42,8 +42,11 @@ export interface HotspotSimpleListProps { hotspotsTotal: number; loadingMore: boolean; onHotspotClick: (hotspot: RawHotspot) => void; + onLocationClick: (index: number) => void; + onScroll: (element: Element) => void; onLoadMore: () => void; selectedHotspot: RawHotspot; + selectedHotspotLocation?: number; standards: Standards; } @@ -65,6 +68,7 @@ export default class HotspotSimpleList extends React.Component<HotspotSimpleList hotspotsTotal, loadingMore, selectedHotspot, + selectedHotspotLocation, standards } = this.props; @@ -110,7 +114,10 @@ export default class HotspotSimpleList extends React.Component<HotspotSimpleList <HotspotListItem hotspot={h} onClick={this.props.onHotspotClick} + onLocationClick={this.props.onLocationClick} + onScroll={this.props.onScroll} selected={h.key === selectedHotspot.key} + selectedHotspotLocation={selectedHotspotLocation} /> </li> ))} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx index 690f1047d1c..f858ee711e4 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { scrollToElement } from '../../../helpers/scrolling'; import { getSources } from '../../../api/components'; import { locationsByLine } from '../../../components/SourceViewer/helpers/indexing'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; @@ -32,6 +33,8 @@ interface Props { component: Component; hotspot: Hotspot; onCommentButtonClick: () => void; + onLocationSelect: (index: number) => void; + selectedHotspotLocation?: number; } interface State { @@ -195,8 +198,12 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat this.setState({ highlightedSymbols }); }; + handleScroll = (element: Element, offset = window.innerHeight / 2, smooth = true) => { + scrollToElement(element, { topOffset: offset - 100, bottomOffset: offset, smooth }); + }; + render() { - const { branchLike, component, hotspot } = this.props; + const { branchLike, component, hotspot, selectedHotspotLocation } = this.props; const { highlightedSymbols, lastLine, loading, sourceLines, secondaryLocations } = this.state; const locations = locationsByLine([hotspot]); @@ -214,9 +221,12 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat onCommentButtonClick={this.props.onCommentButtonClick} onExpandBlock={this.handleExpansion} onSymbolClick={this.handleSymbolClick} + onLocationSelect={this.props.onLocationSelect} + onScroll={this.handleScroll} sourceLines={sourceLines} sourceViewerFile={sourceViewerFile} secondaryLocations={secondaryLocations} + selectedHotspotLocation={selectedHotspotLocation} /> ); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx index 80998643a2f..459c1610fe1 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx @@ -40,9 +40,12 @@ export interface HotspotSnippetContainerRendererProps { hotspot: Hotspot; loading: boolean; locations: { [line: number]: LinearIssueLocation[] }; + selectedHotspotLocation?: number; onCommentButtonClick: () => void; onExpandBlock: (direction: ExpandDirection) => Promise<void>; onSymbolClick: (symbols: string[]) => void; + onLocationSelect: (index: number) => void; + onScroll: (element: Element) => void; sourceLines: SourceLine[]; sourceViewerFile: SourceViewerFile; secondaryLocations: FlowLocation[]; @@ -63,7 +66,8 @@ export default function HotspotSnippetContainerRenderer( sourceLines, sourceViewerFile, secondaryLocations, - component + component, + selectedHotspotLocation } = props; const renderHotspotBoxInLine = (lineNumber: number) => @@ -73,6 +77,11 @@ export default function HotspotSnippetContainerRenderer( undefined ); + const highlightedLocation = + selectedHotspotLocation !== undefined + ? { index: selectedHotspotLocation, text: hotspot.message } + : undefined; + return ( <> {!loading && sourceLines.length === 0 && ( @@ -91,7 +100,7 @@ export default function HotspotSnippetContainerRenderer( handleCloseIssues={noop} handleOpenIssues={noop} handleSymbolClick={props.onSymbolClick} - highlightedLocationMessage={undefined} + highlightedLocationMessage={highlightedLocation} highlightedSymbols={highlightedSymbols} index={0} issue={hotspot} @@ -101,11 +110,12 @@ export default function HotspotSnippetContainerRenderer( locationsByLine={primaryLocations} onIssueChange={noop} onIssuePopupToggle={noop} - onLocationSelect={noop} + onLocationSelect={props.onLocationSelect} openIssuesByLine={{}} renderAdditionalChildInLine={renderHotspotBoxInLine} renderDuplicationPopup={noop} snippet={sourceLines} + scroll={props.onScroll} /> )} </DeferredSpinner> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx index 860c97f2bbb..14c648765fd 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewer.tsx @@ -35,6 +35,8 @@ interface Props { hotspotsReviewedMeasure?: string; onSwitchStatusFilter: (option: HotspotStatusFilter) => void; onUpdateHotspot: (hotspotKey: string) => Promise<void>; + onLocationClick: (index: number) => void; + selectedHotspotLocation?: number; } interface State { @@ -116,7 +118,7 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { }; render() { - const { component, hotspotsReviewedMeasure } = this.props; + const { component, hotspotsReviewedMeasure, selectedHotspotLocation } = this.props; const { hotspot, lastStatusChangedTo, loading, showStatusUpdateSuccessModal } = this.state; return ( @@ -131,7 +133,9 @@ export default class HotspotViewer extends React.PureComponent<Props, State> { onSwitchFilterToStatusOfUpdatedHotspot={this.handleSwitchFilterToStatusOfUpdatedHotspot} onShowCommentForm={this.handleScrollToCommentForm} onUpdateHotspot={this.handleHotspotUpdate} + onLocationClick={this.props.onLocationClick} showStatusUpdateSuccessModal={showStatusUpdateSuccessModal} + selectedHotspotLocation={selectedHotspotLocation} /> ); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx index a46ab94816b..0fc2f8540b0 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx @@ -42,7 +42,9 @@ export interface HotspotViewerRendererProps { onUpdateHotspot: (statusUpdate?: boolean, statusOption?: HotspotStatusOption) => Promise<void>; onShowCommentForm: () => void; onSwitchFilterToStatusOfUpdatedHotspot: () => void; + onLocationClick: (index: number) => void; showStatusUpdateSuccessModal: boolean; + selectedHotspotLocation?: number; } export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { @@ -54,7 +56,8 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { loading, lastStatusChangedTo, showStatusUpdateSuccessModal, - commentTextRef + commentTextRef, + selectedHotspotLocation } = props; return ( @@ -78,9 +81,12 @@ export function HotspotViewerRenderer(props: HotspotViewerRendererProps) { component={component} hotspot={hotspot} onCommentButtonClick={props.onShowCommentForm} + onLocationSelect={props.onLocationClick} + selectedHotspotLocation={selectedHotspotLocation} /> } hotspot={hotspot} + selectedHotspotLocation={selectedHotspotLocation} /> <HotspotReviewHistoryAndComments commentTextRef={commentTextRef} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx index 3b5cea5cc0a..bb4faab83bf 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx @@ -26,6 +26,7 @@ import { Hotspot } from '../../../types/security-hotspots'; interface Props { codeTabContent: React.ReactNode; hotspot: Hotspot; + selectedHotspotLocation?: number; } interface State { @@ -63,6 +64,14 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State> currentTab: tabs[0], tabs }); + } else if ( + this.props.selectedHotspotLocation !== undefined && + this.props.selectedHotspotLocation !== prevProps.selectedHotspotLocation + ) { + const { tabs } = this.state; + this.setState({ + currentTab: tabs[0] + }); } } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx index cc1f787a8d4..55f7f5c6dc0 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx @@ -75,6 +75,8 @@ function shallowRender(props: Partial<HotspotCategoryProps> = {}) { selectedHotspot={mockRawHotspot()} title="Class Injection" isLastAndIncomplete={false} + onLocationClick={jest.fn()} + onScroll={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx index 4d21f225d7a..2d9aa8ea056 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx @@ -98,6 +98,19 @@ it('should update grouped hotspots when the list changes', () => { expect(wrapper.state().groupedHotspots[0].categories[0].hotspots).toHaveLength(1); }); +it('should expand the categories for which the location is selected', () => { + const wrapper = shallowRender({ hotspots, selectedHotspot: hotspots[0] }); + + expect(wrapper.state().expandedCategories).toEqual({ cat2: true }); + + wrapper.instance().handleToggleCategory('cat2', false); + expect(wrapper.state().expandedCategories).toEqual({ cat2: false }); + + wrapper.setProps({ selectedHotspotLocation: 1 }); + + expect(wrapper.state().expandedCategories).toEqual({ cat2: true }); +}); + function shallowRender(props: Partial<HotspotList['props']> = {}) { return shallow<HotspotList>( <HotspotList @@ -107,6 +120,8 @@ function shallowRender(props: Partial<HotspotList['props']> = {}) { loadingMore={false} onHotspotClick={jest.fn()} onLoadMore={jest.fn()} + onLocationClick={jest.fn()} + onScroll={jest.fn()} securityCategories={{}} selectedHotspot={mockRawHotspot({ key: 'h2' })} statusFilter={HotspotStatusFilter.TO_REVIEW} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx index e5090040fac..09187d1f218 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx @@ -39,6 +39,13 @@ it('should handle click', () => { function shallowRender(props: Partial<HotspotListItemProps> = {}) { return shallow( - <HotspotListItem hotspot={mockRawHotspot()} onClick={jest.fn()} selected={false} {...props} /> + <HotspotListItem + hotspot={mockRawHotspot()} + onClick={jest.fn()} + onScroll={jest.fn()} + onLocationClick={jest.fn} + selected={false} + {...props} + /> ); } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx index 525156472ae..a74de8f1277 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx @@ -64,6 +64,8 @@ function shallowRender(props: Partial<HotspotSimpleListProps> = {}) { loadingMore={false} onHotspotClick={jest.fn()} onLoadMore={jest.fn()} + onLocationClick={jest.fn()} + onScroll={jest.fn()} selectedHotspot={hotspots[0]} standards={{ cwe: { 327: { title: 'Use of a Broken or Risky Cryptographic Algorithm' } }, diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx index 98edc5eddf2..8a0fb0e734d 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx @@ -20,6 +20,8 @@ import { shallow } from 'enzyme'; import { range } from 'lodash'; import * as React from 'react'; +import { mockHtmlElement } from '../../../../helpers/mocks/dom'; +import { scrollToElement } from '../../../../helpers/scrolling'; import { getSources } from '../../../../api/components'; import { mockBranch } from '../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../helpers/mocks/component'; @@ -34,6 +36,10 @@ jest.mock('../../../../api/components', () => ({ getSources: jest.fn().mockResolvedValue([]) })); +jest.mock('../../../../helpers/scrolling', () => ({ + scrollToElement: jest.fn() +})); + beforeEach(() => jest.clearAllMocks()); const branch = mockBranch(); @@ -214,6 +220,18 @@ it('should handle symbol click', () => { expect(wrapper.state().highlightedSymbols).toBe(symbols); }); +it('should handle scroll properly', async () => { + const wrapper = shallowRender(); + const element = mockHtmlElement(); + wrapper.instance().handleScroll(element, 200); + await waitAndUpdate(wrapper); + expect(scrollToElement).toBeCalledWith(element, { + bottomOffset: 200, + smooth: true, + topOffset: 100 + }); +}); + function shallowRender(props?: Partial<HotspotSnippetContainer['props']>) { return shallow<HotspotSnippetContainer>( <HotspotSnippetContainer @@ -221,6 +239,7 @@ function shallowRender(props?: Partial<HotspotSnippetContainer['props']>) { component={mockComponent()} hotspot={mockHotspot()} onCommentButtonClick={jest.fn()} + onLocationSelect={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx index 2b36ee19b29..5afde638a1a 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx @@ -45,6 +45,13 @@ it('should render a HotspotPrimaryLocationBox', () => { expect(renderAdditionalChildInLine!(42)).not.toBeUndefined(); }); +it('should render correctly when secondary location is selected', () => { + const wrapper = shallowRender({ + selectedHotspotLocation: 1 + }); + expect(wrapper).toMatchSnapshot('with selected hotspot location'); +}); + function shallowRender(props?: Partial<HotspotSnippetContainerRendererProps>) { return shallow( <HotspotSnippetContainerRenderer @@ -60,6 +67,8 @@ function shallowRender(props?: Partial<HotspotSnippetContainerRendererProps>) { sourceLines={[]} sourceViewerFile={mockSourceViewerFile()} component={mockComponent()} + onLocationSelect={jest.fn()} + onScroll={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx index 31a07d26364..264e81a5812 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx @@ -143,6 +143,7 @@ function shallowRender(props?: Partial<HotspotViewer['props']>) { hotspotKey={hotspotKey} onSwitchStatusFilter={jest.fn()} onUpdateHotspot={jest.fn()} + onLocationClick={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx index d99c07fc360..391389ef308 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx @@ -64,6 +64,7 @@ function shallowRender(props?: Partial<HotspotViewerRendererProps>) { onShowCommentForm={jest.fn()} onUpdateHotspot={jest.fn()} showStatusUpdateSuccessModal={false} + onLocationClick={jest.fn()} {...props} /> ); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx index 941b24e0364..2ba96c577f8 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx @@ -96,6 +96,23 @@ it('should select first tab on hotspot update', () => { expect(wrapper.state().currentTab.key).toBe(TabKeys.Code); }); +it('should select first tab when hotspot location is selected and is not undefined', () => { + const wrapper = shallowRender(); + const onSelect: (tab: TabKeys) => void = wrapper.find(BoxedTabs).prop('onSelect'); + + onSelect(TabKeys.VulnerabilityDescription); + expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); + + wrapper.setProps({ selectedHotspotLocation: 1 }); + expect(wrapper.state().currentTab.key).toBe(TabKeys.Code); + + onSelect(TabKeys.VulnerabilityDescription); + expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); + + wrapper.setProps({ selectedHotspotLocation: undefined }); + expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); +}); + function shallowRender(props?: Partial<HotspotViewerTabs['props']>) { return shallow<HotspotViewerTabs>( <HotspotViewerTabs diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap index cd2664c3ad4..766b906c75a 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotCategory-test.tsx.snap @@ -49,6 +49,8 @@ exports[`should render correctly with hotspots 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -75,6 +77,8 @@ exports[`should render correctly with hotspots 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -159,6 +163,8 @@ exports[`should render correctly with hotspots: contains selected 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={true} /> </li> @@ -185,6 +191,8 @@ exports[`should render correctly with hotspots: contains selected 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -242,6 +250,8 @@ exports[`should render correctly with hotspots: lastAndIncomplete 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -268,6 +278,8 @@ exports[`should render correctly with hotspots: lastAndIncomplete 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap index f506600e1ad..9fd195681f6 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotList-test.tsx.snap @@ -131,6 +131,8 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -179,6 +181,8 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -262,6 +266,8 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -310,6 +316,8 @@ exports[`should render correctly with hotspots: no pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -403,6 +411,8 @@ exports[`should render correctly with hotspots: pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -451,6 +461,8 @@ exports[`should render correctly with hotspots: pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -534,6 +546,8 @@ exports[`should render correctly with hotspots: pagination 1`] = ` } isLastAndIncomplete={false} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { @@ -582,6 +596,8 @@ exports[`should render correctly with hotspots: pagination 1`] = ` } isLastAndIncomplete={true} onHotspotClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} onToggleExpand={[Function]} selectedHotspot={ Object { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap index 0d0f163550e..41a9dbae849 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotListItem-test.tsx.snap @@ -61,7 +61,7 @@ exports[`should render correctly 2`] = ` isCrossFile={false} locations={Array []} onLocationSelect={[Function]} - scroll={[Function]} + scroll={[MockFunction]} uniqueKey="01fc972e-2a3c-433e-bcae-0bd7f88f5123" /> </div> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap index 950f99e0f46..b7e685bd3f0 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSimpleList-test.tsx.snap @@ -53,6 +53,8 @@ exports[`should render correctly: filter by both 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={true} /> </li> @@ -79,6 +81,8 @@ exports[`should render correctly: filter by both 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -145,6 +149,8 @@ exports[`should render correctly: filter by category 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={true} /> </li> @@ -171,6 +177,8 @@ exports[`should render correctly: filter by category 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -237,6 +245,8 @@ exports[`should render correctly: filter by cwe 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={true} /> </li> @@ -263,6 +273,8 @@ exports[`should render correctly: filter by cwe 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> @@ -339,6 +351,8 @@ exports[`should render correctly: filter by file 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={true} /> </li> @@ -365,6 +379,8 @@ exports[`should render correctly: filter by file 1`] = ` } } onClick={[MockFunction]} + onLocationClick={[MockFunction]} + onScroll={[MockFunction]} selected={false} /> </li> diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap index eae97ea5a08..bf4192ecb35 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap @@ -133,6 +133,8 @@ exports[`should render correctly 1`] = ` } onCommentButtonClick={[MockFunction]} onExpandBlock={[Function]} + onLocationSelect={[MockFunction]} + onScroll={[Function]} onSymbolClick={[Function]} secondaryLocations={ Array [ diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap index 5a9f5912a14..71d62cac55b 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap @@ -136,6 +136,142 @@ exports[`should render correctly 1`] = ` </Fragment> `; +exports[`should render correctly when secondary location is selected: with selected hotspot location 1`] = ` +<Fragment> + <p + className="spacer-bottom" + > + hotspots.no_associated_lines + </p> + <Connect(withCurrentUser(HotspotSnippetHeader)) + branchLike={ + Object { + "analysisDate": "2018-01-01", + "excludedFromPurge": true, + "isMain": true, + "name": "master", + } + } + component={ + Object { + "breadcrumbs": Array [], + "key": "my-project", + "name": "MyProject", + "qualifier": "TRK", + "qualityGate": Object { + "isDefault": true, + "key": "30", + "name": "Sonar way", + }, + "qualityProfiles": Array [ + Object { + "deleted": false, + "key": "my-qp", + "language": "ts", + "name": "Sonar way", + }, + ], + "tags": Array [], + } + } + hotspot={ + Object { + "assignee": "assignee", + "assigneeUser": Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + "author": "author", + "authorUser": Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + "canChangeStatus": true, + "changelog": Array [], + "comment": Array [], + "component": Object { + "key": "hotspot-component", + "longName": "Hotspot component long name", + "name": "Hotspot Component", + "path": "path/to/component", + "qualifier": "FIL", + }, + "creationDate": "2013-05-13T17:55:41+0200", + "flows": Array [ + Object { + "locations": Array [ + Object { + "component": "main.js", + "textRange": Object { + "endLine": 2, + "endOffset": 2, + "startLine": 1, + "startOffset": 1, + }, + }, + ], + }, + ], + "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123", + "line": 142, + "message": "'3' is a magic number.", + "project": Object { + "key": "hotspot-component", + "longName": "Hotspot component long name", + "name": "Hotspot Component", + "path": "path/to/component", + "qualifier": "TRK", + }, + "resolution": "FIXED", + "rule": Object { + "fixRecommendations": "<p>This a <strong>strong</strong> message about fixing !</p>", + "key": "squid:S2077", + "name": "That rule", + "riskDescription": "<p>This a <strong>strong</strong> message about risk !</p>", + "securityCategory": "sql-injection", + "vulnerabilityDescription": "<p>This a <strong>strong</strong> message about vulnerability !</p>", + "vulnerabilityProbability": "HIGH", + }, + "status": "REVIEWED", + "textRange": Object { + "endLine": 142, + "endOffset": 83, + "startLine": 142, + "startOffset": 26, + }, + "updateDate": "2013-05-13T17:55:42+0200", + "users": Array [ + Object { + "active": true, + "local": true, + "login": "assignee", + "name": "John Doe", + }, + Object { + "active": true, + "local": true, + "login": "author", + "name": "John Doe", + }, + ], + } + } + /> + <div + className="bordered" + > + <DeferredSpinner + className="big-spacer" + loading={false} + /> + </div> +</Fragment> +`; + exports[`should render correctly: with sourcelines 1`] = ` <Fragment> <Connect(withCurrentUser(HotspotSnippetHeader)) @@ -380,10 +516,11 @@ exports[`should render correctly: with sourcelines 1`] = ` locationsByLine={Object {}} onIssueChange={[Function]} onIssuePopupToggle={[Function]} - onLocationSelect={[Function]} + onLocationSelect={[MockFunction]} openIssuesByLine={Object {}} renderAdditionalChildInLine={[Function]} renderDuplicationPopup={[Function]} + scroll={[MockFunction]} snippet={ Array [ Object { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap index 0b61ad710a4..1e5149058ec 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewer-test.tsx.snap @@ -31,6 +31,7 @@ exports[`should render correctly 1`] = ` } loading={true} onCloseStatusUpdateSuccessModal={[Function]} + onLocationClick={[MockFunction]} onShowCommentForm={[Function]} onSwitchFilterToStatusOfUpdatedHotspot={[Function]} onUpdateHotspot={[Function]} @@ -74,6 +75,7 @@ exports[`should render correctly 2`] = ` } loading={false} onCloseStatusUpdateSuccessModal={[Function]} + onLocationClick={[MockFunction]} onShowCommentForm={[Function]} onSwitchFilterToStatusOfUpdatedHotspot={[Function]} onUpdateHotspot={[Function]} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap index 7bf90466852..70121ce2eb5 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerRenderer-test.tsx.snap @@ -209,6 +209,7 @@ exports[`should render correctly: anonymous user 1`] = ` } } onCommentButtonClick={[MockFunction]} + onLocationSelect={[MockFunction]} /> } hotspot={ @@ -610,6 +611,7 @@ exports[`should render correctly: assignee without name 1`] = ` } } onCommentButtonClick={[MockFunction]} + onLocationSelect={[MockFunction]} /> } hotspot={ @@ -1011,6 +1013,7 @@ exports[`should render correctly: default 1`] = ` } } onCommentButtonClick={[MockFunction]} + onLocationSelect={[MockFunction]} /> } hotspot={ @@ -1412,6 +1415,7 @@ exports[`should render correctly: deleted assignee 1`] = ` } } onCommentButtonClick={[MockFunction]} + onLocationSelect={[MockFunction]} /> } hotspot={ @@ -1826,6 +1830,7 @@ exports[`should render correctly: show success modal 1`] = ` } } onCommentButtonClick={[MockFunction]} + onLocationSelect={[MockFunction]} /> } hotspot={ @@ -2227,6 +2232,7 @@ exports[`should render correctly: unassigned 1`] = ` } } onCommentButtonClick={[MockFunction]} + onLocationSelect={[MockFunction]} /> } hotspot={ diff --git a/server/sonar-web/src/main/js/components/locations/LocationsList.tsx b/server/sonar-web/src/main/js/components/locations/LocationsList.tsx index 6a38b047a05..19e52f004c0 100644 --- a/server/sonar-web/src/main/js/components/locations/LocationsList.tsx +++ b/server/sonar-web/src/main/js/components/locations/LocationsList.tsx @@ -28,7 +28,6 @@ interface Props { locations: FlowLocation[]; onLocationSelect: (index: number) => void; scroll: (element: Element) => void; - selectedFlowIndex?: number; selectedLocationIndex?: number; } diff --git a/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx b/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx index 48b9868be13..3f525e8845c 100644 --- a/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx +++ b/server/sonar-web/src/main/js/components/locations/__tests__/LocationsList-test.tsx @@ -67,7 +67,6 @@ function shallowRender(overrides: Partial<LocationsList['props']> = {}) { isCrossFile={true} onLocationSelect={jest.fn()} scroll={jest.fn()} - selectedFlowIndex={undefined} selectedLocationIndex={undefined} {...overrides} /> |