From ae779528b341900152976ceefbef862ce715dbe6 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Tue, 28 Jan 2020 08:41:21 +0100 Subject: [PATCH] SONAR-12911 Code viewer is not accessible to blind users --- .../ComponentSourceSnippetGroupViewer.tsx | 29 +--- .../CrossComponentSourceViewerWrapper.tsx | 56 +------- .../SnippetViewer.tsx | 16 +-- ...ComponentSourceSnippetGroupViewer-test.tsx | 20 --- ...CrossComponentSourceViewerWrapper-test.tsx | 27 ---- .../__tests__/SnippetViewer-test.tsx | 4 - ...ComponentSourceViewerWrapper-test.tsx.snap | 2 +- .../__snapshots__/SnippetViewer-test.tsx.snap | 17 --- .../components/HotspotSnippetContainer.tsx | 23 +--- .../HotspotSnippetContainerRenderer.tsx | 6 - .../HotspotSnippetContainer-test.tsx | 26 ---- .../HotspotSnippetContainerRenderer-test.tsx | 2 - .../HotspotSnippetContainer-test.tsx.snap | 1 - ...spotSnippetContainerRenderer-test.tsx.snap | 2 - .../SourceViewer/SourceViewerBase.tsx | 48 +------ .../SourceViewer/SourceViewerCode.tsx | 4 - .../SourceViewerBase-test.tsx.snap | 1 - .../components/DuplicationPopup.tsx | 124 +++++++++--------- .../SourceViewer/components/Line.css | 34 ++--- .../SourceViewer/components/Line.tsx | 116 ++++++++-------- .../SourceViewer/components/LineCoverage.tsx | 12 +- .../components/LineDuplicationBlock.tsx | 94 ++++++------- .../components/LineDuplications.tsx | 60 --------- .../components/LineIssuesIndicator.tsx | 56 ++++---- .../SourceViewer/components/LineNumber.tsx | 66 ++++------ .../components/LineOptionsPopup.tsx | 31 ++--- .../SourceViewer/components/LineSCM.tsx | 70 +++++----- .../SourceViewer/components/SCMPopup.tsx | 43 +++--- .../components/__tests__/Line-test.tsx | 2 - .../__tests__/LineCoverage-test.tsx | 34 ++--- .../__tests__/LineDuplicationBlock-test.tsx | 64 +++++---- .../__tests__/LineDuplications-test.tsx | 39 ------ .../__tests__/LineIssuesIndicator-test.tsx | 31 ++--- .../components/__tests__/LineNumber-test.tsx | 19 +-- .../__tests__/LineOptionsPopup-test.tsx | 4 +- .../components/__tests__/LineSCM-test.tsx | 59 +++------ .../components/__tests__/SCMPopup-test.tsx | 26 +++- .../__snapshots__/Line-test.tsx.snap | 81 +++--------- .../__snapshots__/LineCoverage-test.tsx.snap | 42 +++++- .../LineDuplicationBlock-test.tsx.snap | 46 ++++--- .../LineDuplications-test.tsx.snap | 29 ---- .../LineIssuesIndicator-test.tsx.snap | 46 ++++--- .../__snapshots__/LineNumber-test.tsx.snap | 33 ++--- .../LineOptionsPopup-test.tsx.snap | 51 ++++--- .../__snapshots__/LineSCM-test.tsx.snap | 108 ++++++--------- .../__snapshots__/SCMPopup-test.tsx.snap | 111 +++++++++++++--- .../resources/org/sonar/l10n/core.properties | 8 ++ 47 files changed, 752 insertions(+), 1071 deletions(-) delete mode 100644 server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplications.tsx delete mode 100644 server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplications-test.tsx delete mode 100644 server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplications-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx index 016b7ddae89..2cc7de55fe9 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx @@ -17,7 +17,6 @@ * 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 classNames from 'classnames'; import * as React from 'react'; import { getSources } from '../../../api/components'; import getCoverageStatus from '../../../components/SourceViewer/helpers/getCoverageStatus'; @@ -43,12 +42,10 @@ interface Props { issuePopup?: { issue: string; name: string }; issuesByLine: T.IssuesByLine; lastSnippetGroup: boolean; - linePopup?: T.LinePopup; loadDuplications: (component: string, line: T.SourceLine) => void; locations: T.FlowLocation[]; onIssueChange: (issue: T.Issue) => void; onIssuePopupToggle: (issue: string, popupName: string, open?: boolean) => void; - onLinePopupToggle: (linePopup: T.LinePopup & { component: string }) => void; onLocationSelect: (index: number) => void; renderDuplicationPopup: ( component: T.SourceViewerFile, @@ -273,13 +270,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone ); }; - handleLinePopupToggle = (linePopup: T.LinePopup) => { - this.props.onLinePopupToggle({ - ...linePopup, - component: this.props.snippetGroup.component.key - }); - }; - handleOpenIssues = (line: T.SourceLine) => { this.setState(state => ({ openIssuesByLine: { ...state.openIssuesByLine, [line.line]: true } @@ -326,9 +316,10 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone 0 - })} - ref={this.rootNodeRef}> +
{ + fetchDuplications = (component: string) => { getDuplications({ key: component, ...getBranchLikeQuery(this.props.branchLike) }).then( r => { if (this.mounted) { - this.setState(state => ({ + this.setState({ duplicatedFiles: r.files, duplications: r.duplications, - duplicationsByLine: getDuplicationsByLine(r.duplications), - linePopup: - r.duplications.length === 1 - ? { component, index: 0, line: line.line, name: 'duplications' } - : state.linePopup - })); + duplicationsByLine: getDuplicationsByLine(r.duplications) + }); } }, () => {} @@ -119,7 +114,6 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone this.setState({ components, issuePopup: undefined, - linePopup: undefined, loading: false }); if (this.props.onLoaded) { @@ -151,33 +145,6 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone }); }; - handleLinePopupToggle = ({ - component, - index, - line, - name, - open - }: T.LinePopup & { component: string }) => { - this.setState((state: State) => { - const samePopup = - state.linePopup !== undefined && - state.linePopup.line === line && - state.linePopup.name === name && - state.linePopup.component === component && - state.linePopup.index === index; - if (open !== false && !samePopup) { - return { linePopup: { component, index, line, name } }; - } else if (open !== true && samePopup) { - return { linePopup: undefined }; - } - return null; - }); - }; - - handleCloseLinePopup = () => { - this.setState({ linePopup: undefined }); - }; - renderDuplicationPopup = (component: T.SourceViewerFile, index: number, line: number) => { const { duplicatedFiles, duplications } = this.state; @@ -195,7 +162,6 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone branchLike={this.props.branchLike} duplicatedFiles={duplicatedFiles} inRemovedComponent={isDuplicationBlockInRemovedComponent(blocks)} - onClose={this.handleCloseLinePopup} openComponent={openComponent} sourceViewerFile={component} /> @@ -224,21 +190,13 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone } const { issue, locations } = this.props; - const { components, duplications, duplicationsByLine, linePopup } = this.state; + const { components, duplications, duplicationsByLine } = this.state; const issuesByComponent = issuesByComponentAndLine(this.props.issues); const locationsByComponent = groupLocationsByComponent(issue, locations, components); return (
{locationsByComponent.map((snippetGroup, i) => { - let componentProps = {}; - if (linePopup && snippetGroup.component.key === linePopup.component) { - componentProps = { - duplications, - duplicationsByLine, - linePopup: { index: linePopup.index, line: linePopup.line, name: linePopup.name } - }; - } return ( ); diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx index 995ce6bb18c..c3931a05843 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx @@ -42,7 +42,6 @@ interface Props { duplicationsByLine?: { [line: number]: number[] }; expandBlock: (snippetIndex: number, direction: T.ExpandDirection) => Promise; handleCloseIssues: (line: T.SourceLine) => void; - handleLinePopupToggle: (line: T.SourceLine) => void; handleOpenIssues: (line: T.SourceLine) => void; handleSymbolClick: (symbols: string[]) => void; highlightedLocationMessage: { index: number; text: string | undefined } | undefined; @@ -52,8 +51,7 @@ interface Props { issuePopup?: { issue: string; name: string }; issuesByLine: T.IssuesByLine; lastSnippetOfLastGroup: boolean; - linePopup?: T.LinePopup; - loadDuplications: (line: T.SourceLine) => void; + loadDuplications?: (line: T.SourceLine) => void; locations: T.FlowLocation[]; locationsByLine: { [line: number]: T.LinearIssueLocation[] }; onIssueChange: (issue: T.Issue) => void; @@ -138,6 +136,7 @@ export default class SnippetViewer extends React.PureComponent { (duplicationsCount && duplicationsByLine && duplicationsByLine[line.line]) || []; const isSinkLine = issuesForLine.some(i => i.key === this.props.issue.key); + const noop = () => {}; return ( { key={line.line} last={false} line={line} - linePopup={this.props.linePopup} - loadDuplications={this.props.loadDuplications} + loadDuplications={this.props.loadDuplications || noop} onIssueChange={this.props.onIssueChange} onIssuePopupToggle={this.props.onIssuePopupToggle} - onIssueSelect={() => {}} - onIssueUnselect={() => {}} + onIssueSelect={noop} + onIssueUnselect={noop} onIssuesClose={this.props.handleCloseIssues} onIssuesOpen={this.props.handleOpenIssues} - onLinePopupToggle={this.props.handleLinePopupToggle} onLocationSelect={this.props.onLocationSelect} onSymbolClick={this.props.handleSymbolClick} openIssues={this.props.openIssuesByLine[line.line]} @@ -211,7 +208,8 @@ export default class SnippetViewer extends React.PureComponent { ? Math.max(0, LINES_BELOW_ISSUE - (bottomLine - lowestVisibleIssue)) : 0; - const displayDuplications = snippet.some(s => !!s.duplicated); + const displayDuplications = + Boolean(this.props.loadDuplications) && snippet.some(s => !!s.duplicated); return (
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx index f68a22f601b..6132a637b9f 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx @@ -221,12 +221,10 @@ it('should correctly handle lines actions', () => { ...mockSnippetsByComponent('a', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56]) }; const loadDuplications = jest.fn(); - const onLinePopupToggle = jest.fn(); const renderDuplicationPopup = jest.fn(); const wrapper = shallowRender({ loadDuplications, - onLinePopupToggle, renderDuplicationPopup, snippetGroup }); @@ -238,12 +236,6 @@ it('should correctly handle lines actions', () => { .prop('loadDuplications')(line); expect(loadDuplications).toHaveBeenCalledWith('a', line); - wrapper - .find('SnippetViewer') - .first() - .prop('handleLinePopupToggle')({ line: 13, name: 'foo' }); - expect(onLinePopupToggle).toHaveBeenCalledWith({ component: 'a', line: 13, name: 'foo' }); - wrapper .find('SnippetViewer') .first() @@ -264,18 +256,14 @@ describe('getNodes', () => { const wrapper = mount( { const wrapper = mount( ( { expect(wrapper.state('issuePopup')).toBeUndefined(); }); -it('should handle line popup', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - const linePopup = { component: 'foo', index: 0, line: 16, name: 'b.tsx' }; - wrapper.find('ComponentSourceSnippetGroupViewer').prop('onLinePopupToggle')(linePopup); - expect(wrapper.state('linePopup')).toEqual(linePopup); - - wrapper.find('ComponentSourceSnippetGroupViewer').prop('onLinePopupToggle')(linePopup); - expect(wrapper.state('linePopup')).toBeUndefined(); - - const openLinePopup = { ...linePopup, open: true }; - wrapper.find('ComponentSourceSnippetGroupViewer').prop('onLinePopupToggle')( - openLinePopup - ); - wrapper.find('ComponentSourceSnippetGroupViewer').prop('onLinePopupToggle')( - openLinePopup - ); - expect(wrapper.state('linePopup')).toEqual(linePopup); -}); - it('should handle duplication popup', async () => { const files = { b: { key: 'b', name: 'B.tsx', project: 'foo', projectName: 'Foo' } }; const duplications = [{ blocks: [{ _ref: '1', from: 1, size: 2 }] }]; @@ -126,12 +105,6 @@ it('should handle duplication popup', async () => { expect(wrapper.state('duplicatedFiles')).toEqual(files); expect(wrapper.state('duplications')).toEqual(duplications); expect(wrapper.state('duplicationsByLine')).toEqual({ '1': [0], '2': [0] }); - expect(wrapper.state('linePopup')).toEqual({ - component: 'foo', - index: 0, - line: 16, - name: 'duplications' - }); expect( wrapper.find('ComponentSourceSnippetGroupViewer').prop('renderDuplicationPopup')( diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx index 24bc147e95c..7f9475c4709 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx @@ -127,7 +127,6 @@ function shallowRender(props: Partial = {}) { duplicationsByLine={undefined} expandBlock={jest.fn()} handleCloseIssues={jest.fn()} - handleLinePopupToggle={jest.fn()} handleOpenIssues={jest.fn()} handleSymbolClick={jest.fn()} highlightedLocationMessage={{ index: 0, text: '' }} @@ -136,7 +135,6 @@ function shallowRender(props: Partial = {}) { issue={mockIssue()} issuesByLine={{}} lastSnippetOfLastGroup={false} - linePopup={undefined} loadDuplications={jest.fn()} locations={[]} locationsByLine={{}} @@ -161,7 +159,6 @@ function mountRender(props: Partial = {}) { duplicationsByLine={undefined} expandBlock={jest.fn()} handleCloseIssues={jest.fn()} - handleLinePopupToggle={jest.fn()} handleOpenIssues={jest.fn()} handleSymbolClick={jest.fn()} highlightedLocationMessage={{ index: 0, text: '' }} @@ -170,7 +167,6 @@ function mountRender(props: Partial = {}) { issue={mockIssue()} issuesByLine={{}} lastSnippetOfLastGroup={false} - linePopup={undefined} loadDuplications={jest.fn()} locations={[]} locationsByLine={{}} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap index a4c84ff6a85..f4e8122cdf2 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap @@ -48,6 +48,7 @@ exports[`should render correctly 2`] = ` } > { - const { component, index, line, name, open } = params; - this.setState((state: State) => { - const samePopup = - state.linePopup !== undefined && - state.linePopup.line === line && - state.linePopup.name === name && - state.linePopup.component === component && - state.linePopup.index === index; - if (open !== false && !samePopup) { - return { linePopup: params }; - } else if (open !== true && samePopup) { - return { linePopup: undefined }; - } - return null; - }); - }; - handleSymbolClick = (highlightedSymbols: string[]) => { this.setState({ highlightedSymbols }); }; render() { const { branchLike, component, hotspot } = this.props; - const { highlightedSymbols, lastLine, linePopup, loading, sourceLines } = this.state; + const { highlightedSymbols, lastLine, loading, sourceLines } = this.state; const locations = locationsByLine([hotspot]); @@ -180,11 +161,9 @@ export default class HotspotSnippetContainer extends React.Component Promise; - onLinePopupToggle: (line: T.SourceLine) => void; onSymbolClick: (symbols: string[]) => void; sourceLines: T.SourceLine[]; sourceViewerFile: T.SourceViewerFile; @@ -51,7 +49,6 @@ export default function HotspotSnippetContainerRenderer( displayProjectName, highlightedSymbols, hotspot, - linePopup, loading, locations, sourceLines, @@ -79,7 +76,6 @@ export default function HotspotSnippetContainerRenderer( displaySCM={false} expandBlock={(_i, direction) => props.onExpandBlock(direction)} handleCloseIssues={noop} - handleLinePopupToggle={props.onLinePopupToggle} handleOpenIssues={noop} handleSymbolClick={props.onSymbolClick} highlightedLocationMessage={undefined} @@ -88,8 +84,6 @@ export default function HotspotSnippetContainerRenderer( issue={hotspot} issuesByLine={{}} lastSnippetOfLastGroup={false} - linePopup={linePopup} - loadDuplications={noop} locations={[]} locationsByLine={locations} onIssueChange={noop} 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 112ab0766de..69445d2298b 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 @@ -155,32 +155,6 @@ describe('Expansion', () => { }); }); -it('should handle line popups', async () => { - (getSources as jest.Mock).mockResolvedValueOnce( - range(5, 18).map(line => mockSourceLine({ line })) - ); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - const params = wrapper.state().sourceLines[0]; - - wrapper - .find(HotspotSnippetContainerRenderer) - .props() - .onLinePopupToggle(params); - - expect(wrapper.state().linePopup).toEqual(params); - - // Close it - wrapper - .find(HotspotSnippetContainerRenderer) - .props() - .onLinePopupToggle(params); - - expect(wrapper.state().linePopup).toBeUndefined(); -}); - it('should handle symbol click', () => { const wrapper = shallowRender(); const symbols = ['symbol']; 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 c016e8b5f9f..3ae268d10c3 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 @@ -39,11 +39,9 @@ function shallowRender(props?: Partial) { highlightedSymbols={[]} hotspot={mockHotspot()} lastLine={undefined} - linePopup={undefined} loading={false} locations={{}} onExpandBlock={jest.fn()} - onLinePopupToggle={jest.fn()} onSymbolClick={jest.fn()} sourceLines={[]} sourceViewerFile={mockSourceViewerFile()} 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 75e4dce0d96..c0e3e1f862a 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 @@ -124,7 +124,6 @@ exports[`should render correctly 1`] = ` } } onExpandBlock={[Function]} - onLinePopupToggle={[Function]} onSymbolClick={[Function]} sourceLines={Array []} sourceViewerFile={ 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 772a3ad35f3..f1808647b9b 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 @@ -135,7 +135,6 @@ exports[`should render correctly: with sourcelines 1`] = ` displaySCM={false} expandBlock={[Function]} handleCloseIssues={[Function]} - handleLinePopupToggle={[MockFunction]} handleOpenIssues={[Function]} handleSymbolClick={[MockFunction]} highlightedSymbols={Array []} @@ -241,7 +240,6 @@ exports[`should render correctly: with sourcelines 1`] = ` } issuesByLine={Object {}} lastSnippetOfLastGroup={false} - loadDuplications={[Function]} locations={Array []} locationsByLine={Object {}} onIssueChange={[Function]} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx index e4588216300..abce4a8a4e5 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx @@ -17,7 +17,6 @@ * 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 classNames from 'classnames'; import { intersection, uniqBy } from 'lodash'; import * as React from 'react'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; @@ -96,7 +95,6 @@ export interface Props { interface State { component?: T.SourceViewerFile; - displayDuplications: boolean; duplicatedFiles?: T.Dict; duplications?: T.Duplication[]; duplicationsByLine: { [line: number]: number[] }; @@ -106,7 +104,6 @@ interface State { issuePopup?: { issue: string; name: string }; issues?: T.Issue[]; issuesByLine: { [line: number]: T.Issue[] }; - linePopup?: T.LinePopup; loading: boolean; loadingSourcesAfter: boolean; loadingSourcesBefore: boolean; @@ -136,7 +133,6 @@ export default class SourceViewerBase extends React.PureComponent super(props); this.state = { - displayDuplications: false, duplicationsByLine: {}, hasSourcesAfter: false, highlightedSymbols: [], @@ -245,7 +241,6 @@ export default class SourceViewerBase extends React.PureComponent this.setState( { component, - displayDuplications: false, duplicatedFiles: undefined, duplications: undefined, duplicationsByLine: {}, @@ -254,7 +249,6 @@ export default class SourceViewerBase extends React.PureComponent issueLocationsByLine: locationsByLine(issues), issues, issuesByLine: issuesByLine(issues), - linePopup: undefined, loading: false, notAccessible: false, notExist: false, @@ -474,23 +468,18 @@ export default class SourceViewerBase extends React.PureComponent ); }; - loadDuplications = (line: T.SourceLine) => { + loadDuplications = () => { getDuplications({ key: this.props.component, ...getBranchLikeQuery(this.props.branchLike) }).then( r => { if (this.mounted) { - this.setState(state => ({ - displayDuplications: true, + this.setState({ duplications: r.duplications, duplicationsByLine: duplicationsByLine(r.duplications), - duplicatedFiles: r.files, - linePopup: - r.duplications.length === 1 - ? { index: 0, line: line.line, name: 'duplications' } - : state.linePopup - })); + duplicatedFiles: r.files + }); } }, () => { @@ -499,26 +488,6 @@ export default class SourceViewerBase extends React.PureComponent ); }; - handleLinePopupToggle = ({ index, line, name, open }: T.LinePopup) => { - this.setState((state: State) => { - const samePopup = - state.linePopup !== undefined && - state.linePopup.name === name && - state.linePopup.line === line && - state.linePopup.index === index; - if (open !== false && !samePopup) { - return { linePopup: { index, line, name } }; - } else if (open !== true && samePopup) { - return { linePopup: undefined }; - } - return null; - }); - }; - - closeLinePopup = () => { - this.setState({ linePopup: undefined }); - }; - handleIssuePopupToggle = (issue: string, popupName: string, open?: boolean) => { this.setState((state: State) => { const samePopup = @@ -595,7 +564,6 @@ export default class SourceViewerBase extends React.PureComponent branchLike={this.props.branchLike} duplicatedFiles={duplicatedFiles} inRemovedComponent={isDuplicationBlockInRemovedComponent(blocks)} - onClose={this.closeLinePopup} openComponent={openComponent} sourceViewerFile={component} /> @@ -626,7 +594,6 @@ export default class SourceViewerBase extends React.PureComponent issuePopup={this.state.issuePopup} issues={this.state.issues} issuesByLine={this.state.issuesByLine} - linePopup={this.state.linePopup} loadDuplications={this.loadDuplications} loadSourcesAfter={this.loadSourcesAfter} loadSourcesBefore={this.loadSourcesBefore} @@ -638,7 +605,6 @@ export default class SourceViewerBase extends React.PureComponent onIssueUnselect={this.handleIssueUnselect} onIssuesClose={this.handleCloseIssues} onIssuesOpen={this.handleOpenIssues} - onLinePopupToggle={this.handleLinePopupToggle} onLocationSelect={this.props.onLocationSelect} onSymbolClick={this.handleSymbolClick} openIssuesByLine={this.state.openIssuesByLine} @@ -696,13 +662,9 @@ export default class SourceViewerBase extends React.PureComponent return null; } - const className = classNames('source-viewer', { - 'source-duplications-expanded': this.state.displayDuplications - }); - return ( -
(this.node = node)}> +
(this.node = node)}> {this.renderHeader(this.props.branchLike, component)} {sourceRemoved && ( diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx index 03f68cd8285..85cea2f0def 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx @@ -59,7 +59,6 @@ interface Props { issuePopup: { issue: string; name: string } | undefined; issues: T.Issue[] | undefined; issuesByLine: { [line: number]: T.Issue[] }; - linePopup: T.LinePopup | undefined; loadDuplications: (line: T.SourceLine) => void; loadingSourcesAfter: boolean; loadingSourcesBefore: boolean; @@ -71,7 +70,6 @@ interface Props { onIssueSelect: (issueKey: string) => void; onIssuesOpen: (line: T.SourceLine) => void; onIssueUnselect: () => void; - onLinePopupToggle: (linePopup: T.LinePopup) => void; onLocationSelect: ((index: number) => void) | undefined; onSymbolClick: (symbols: string[]) => void; openIssuesByLine: { [line: number]: boolean }; @@ -143,7 +141,6 @@ export default class SourceViewerCode extends React.PureComponent { key={line.line} last={index === this.props.sources.length - 1 && !this.props.hasSourcesAfter} line={line} - linePopup={this.props.linePopup} loadDuplications={this.props.loadDuplications} onIssueChange={this.props.onIssueChange} onIssuePopupToggle={this.props.onIssuePopupToggle} @@ -151,7 +148,6 @@ export default class SourceViewerCode extends React.PureComponent { onIssueUnselect={this.props.onIssueUnselect} onIssuesClose={this.props.onIssuesClose} onIssuesOpen={this.props.onIssuesOpen} - onLinePopupToggle={this.props.onLinePopupToggle} onLocationSelect={this.props.onLocationSelect} onSymbolClick={this.props.onSymbolClick} openIssues={this.props.openIssuesByLine[line.line] || false} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap index 1c54b627598..6aa4a3556eb 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap @@ -153,7 +153,6 @@ exports[`should render correctly 1`] = ` onIssueUnselect={[Function]} onIssuesClose={[Function]} onIssuesOpen={[Function]} - onLinePopupToggle={[Function]} onSymbolClick={[Function]} openIssuesByLine={Object {}} renderDuplicationPopup={[Function]} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx index 6ccabc9ca16..caa47b2b49b 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx @@ -20,10 +20,8 @@ import { groupBy, sortBy } from 'lodash'; import * as React from 'react'; import { Link } from 'react-router'; -import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; import QualifierIcon from 'sonar-ui-common/components/icons/QualifierIcon'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { collapsedDirFromPath, fileFromPath } from 'sonar-ui-common/helpers/path'; import { isPullRequest } from '../../../helpers/branch-like'; @@ -36,7 +34,6 @@ interface Props { branchLike: BranchLike | undefined; duplicatedFiles?: T.Dict; inRemovedComponent: boolean; - onClose: () => void; openComponent: WorkspaceContextShape['openComponent']; sourceViewerFile: T.SourceViewerFile; } @@ -65,7 +62,6 @@ export default class DuplicationPopup extends React.PureComponent { line: line ? Number(line) : undefined }); } - this.props.onClose(); }; renderDuplication(file: T.DuplicatedFile, children: React.ReactNode, line?: number) { @@ -106,76 +102,74 @@ export default class DuplicationPopup extends React.PureComponent { ); return ( - -
- {this.props.inRemovedComponent && ( - - {translate('duplications.dups_found_on_deleted_resource')} - - )} - {duplications.length > 0 && ( - <> -
- {translate('component_viewer.transition.duplication')} -
- {duplications.map(duplication => ( -
-
- {this.isDifferentComponent(duplication.file, this.props.sourceViewerFile) && ( - <> +
+ {this.props.inRemovedComponent && ( + + {translate('duplications.dups_found_on_deleted_resource')} + + )} + {duplications.length > 0 && ( + <> +
+ {translate('component_viewer.transition.duplication')} +
+ {duplications.map(duplication => ( +
+
+ {this.isDifferentComponent(duplication.file, this.props.sourceViewerFile) && ( + <> +
+ + + {duplication.file.projectName} + +
+ {duplication.file.subProject && duplication.file.subProjectName && (
- - - {duplication.file.projectName} - + + {duplication.file.subProjectName}
- {duplication.file.subProject && duplication.file.subProjectName && ( -
- - {duplication.file.subProjectName} -
- )} - - )} + )} + + )} + + {duplication.file.key !== this.props.sourceViewerFile.key && ( +
+ {this.renderDuplication( + duplication.file, + <> + {collapsedDirFromPath(duplication.file.name)} + + {fileFromPath(duplication.file.name)} + + + )} +
+ )} - {duplication.file.key !== this.props.sourceViewerFile.key && ( -
+
+ {'Lines: '} + {duplication.blocks.map((block, index) => ( + {this.renderDuplication( duplication.file, <> - {collapsedDirFromPath(duplication.file.name)} - - {fileFromPath(duplication.file.name)} - - + {block.from} + {' – '} + {block.from + block.size - 1} + , + block.from )} -
- )} - -
- {'Lines: '} - {duplication.blocks.map((block, index) => ( - - {this.renderDuplication( - duplication.file, - <> - {block.from} - {' – '} - {block.from + block.size - 1} - , - block.from - )} - {index < duplication.blocks.length - 1 && ', '} - - ))} -
+ {index < duplication.blocks.length - 1 && ', '} + + ))}
- ))} - - )} -
- +
+ ))} + + )} +
); } } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css index 0e9531b4f24..50c2c866258 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css @@ -31,6 +31,10 @@ background-color: #f5f5f5; } +.source-line [role='button'] { + cursor: pointer; +} + .source-line-highlighted .source-line-number, .source-line-highlighted:hover .source-line-number, .source-line-highlighted .source-line-issues, @@ -140,10 +144,6 @@ outline: none; } -.source-meta[role='button'] { - cursor: pointer; -} - .source-line-number { min-width: 18px; padding: 0 10px; @@ -152,10 +152,6 @@ text-align: right; } -.source-line-number:before { - content: attr(data-line-number); -} - .source-line-issues { position: relative; padding: 0 2px; @@ -189,17 +185,17 @@ display: none; } -.source-duplications-expanded .source-line-duplications { - display: none; +.source-line-scm { + padding: 0 5px; + background-color: var(--barBackgroundColor); } -.source-duplications-expanded .source-line-duplications-extra { - display: table-cell; +.source-line-scm .dropdown { + display: block; } -.source-line-scm { - padding: 0 5px; - background-color: var(--barBackgroundColor); +.source-line-scm [role='button'] { + height: 18px; } .source-line-scm-inner { @@ -209,19 +205,11 @@ white-space: nowrap; } -.source-line-scm-inner:before { - content: attr(data-author); -} - .source-line-bar { width: 5px; height: 18px; } -.source-line-bar[role='button'] { - cursor: pointer; -} - .source-line-bar:focus { outline: none; } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx index 203321d80a3..b2f63a2e9de 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx @@ -25,7 +25,6 @@ import './Line.css'; import LineCode from './LineCode'; import LineCoverage from './LineCoverage'; import LineDuplicationBlock from './LineDuplicationBlock'; -import LineDuplications from './LineDuplications'; import LineIssuesIndicator from './LineIssuesIndicator'; import LineNumber from './LineNumber'; import LineSCM from './LineSCM'; @@ -50,9 +49,7 @@ interface Props { issues: T.Issue[]; last: boolean; line: T.SourceLine; - linePopup: T.LinePopup | undefined; loadDuplications: (line: T.SourceLine) => void; - onLinePopupToggle: (linePopup: T.LinePopup) => void; onIssueChange: (issue: T.Issue) => void; onIssuePopupToggle: (issueKey: string, popupName: string, open?: boolean) => void; onIssuesClose: (line: T.SourceLine) => void; @@ -73,16 +70,6 @@ interface Props { const LINE_HEIGHT = 18; export default class Line extends React.PureComponent { - isPopupOpen = (name: string, index?: number) => { - const { line, linePopup } = this.props; - return ( - linePopup !== undefined && - linePopup.index === index && - linePopup.line === line.line && - linePopup.name === name - ); - }; - handleIssuesIndicatorClick = () => { if (this.props.openIssues) { this.props.onIssuesClose(this.props.line); @@ -99,45 +86,52 @@ export default class Line extends React.PureComponent { render() { const { + branchLike, + displayAllIssues, displayCoverage, + displayDuplications, + displayIssueLocationsCount, + displayIssueLocationsLink, + displayLocationMarkers, + highlightedLocationMessage, + displayIssues, displaySCM = true, duplications, duplicationsCount, + highlighted, + highlightedSymbols, + issueLocations, issuePopup, - line + issues, + last, + line, + openIssues, + previousLine, + secondaryIssueLocations, + selectedIssue, + verticalBuffer } = this.props; + const className = classNames('source-line', { - 'source-line-highlighted': this.props.highlighted, + 'source-line-highlighted': highlighted, 'source-line-filtered': line.isNew, 'source-line-filtered-dark': displayCoverage && (line.coverageStatus === 'uncovered' || line.coverageStatus === 'partially-covered'), - 'source-line-last': this.props.last === true + 'source-line-last': last === true }); - const bottomPadding = this.props.verticalBuffer - ? this.props.verticalBuffer * LINE_HEIGHT - : undefined; + const bottomPadding = verticalBuffer ? verticalBuffer * LINE_HEIGHT : undefined; return ( - + - {displaySCM && ( - - )} - {this.props.displayIssues && !this.props.displayAllIssues ? ( + {displaySCM && } + {displayIssues && !displayAllIssues ? ( @@ -145,34 +139,42 @@ export default class Line extends React.PureComponent { )} - {this.props.displayDuplications && ( - - )} - - {times(duplicationsCount, index => ( + {displayDuplications && ( 0} + duplicated={Boolean(line.duplicated)} + index={0} + key={0} line={this.props.line} - onPopupToggle={this.props.onLinePopupToggle} - popupOpen={this.isPopupOpen('duplications', index)} + onClick={this.props.loadDuplications} renderDuplicationPopup={this.props.renderDuplicationPopup} /> - ))} + )} + + {duplicationsCount > 1 && + times(duplicationsCount - 1, index => ( + + ))} - {this.props.displayCoverage && } + {displayCoverage && } { onSymbolClick={this.props.onSymbolClick} padding={bottomPadding} scroll={this.props.scroll} - secondaryIssueLocations={this.props.secondaryIssueLocations} - selectedIssue={this.props.selectedIssue} - showIssues={this.props.openIssues || this.props.displayAllIssues} + secondaryIssueLocations={secondaryIssueLocations} + selectedIssue={selectedIssue} + showIssues={openIssues || displayAllIssues} /> ); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx index bb50e7edd27..0f398fc05ef 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx @@ -21,18 +21,20 @@ import * as React from 'react'; import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -interface Props { +export interface LineCoverageProps { line: T.SourceLine; } -export default function LineCoverage({ line }: Props) { +export function LineCoverage({ line }: LineCoverageProps) { const className = 'source-meta source-line-coverage' + (line.coverageStatus != null ? ` source-line-${line.coverageStatus}` : ''); + const status = getStatusTooltip(line); + return ( - -
+ +
); @@ -64,3 +66,5 @@ function getStatusTooltip(line: T.SourceLine) { } return undefined; } + +export default React.memo(LineCoverage); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx index b62c69f8546..fa56de50b62 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx @@ -19,72 +19,64 @@ */ import * as classNames from 'classnames'; import * as React from 'react'; +import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; import Toggler from 'sonar-ui-common/components/controls/Toggler'; import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; +import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; import { translate } from 'sonar-ui-common/helpers/l10n'; -interface Props { +export interface LineDuplicationBlockProps { + blocksLoaded: boolean; duplicated: boolean; index: number; line: T.SourceLine; - onPopupToggle: (linePopup: T.LinePopup) => void; - popupOpen: boolean; + onClick?: (line: T.SourceLine) => void; renderDuplicationPopup: (index: number, line: number) => React.ReactNode; } -export default class LineDuplicationBlock extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - event.currentTarget.blur(); - this.props.onPopupToggle({ - index: this.props.index, - line: this.props.line.line, - name: 'duplications' - }); - }; +export function LineDuplicationBlock(props: LineDuplicationBlockProps) { + const { blocksLoaded, duplicated, index, line } = props; + const [dropdownOpen, setDropdownOpen] = React.useState(false); - handleTogglePopup = (open: boolean) => { - this.props.onPopupToggle({ - index: this.props.index, - line: this.props.line.line, - name: 'duplications', - open - }); - }; + const className = classNames('source-meta', 'source-line-duplications', { + 'source-line-duplicated': duplicated + }); - closePopup = () => { - this.handleTogglePopup(false); - }; - - render() { - const { duplicated, index, line, popupOpen } = this.props; - const className = classNames('source-meta', 'source-line-duplications-extra', { - 'source-line-duplicated': duplicated - }); - - return duplicated ? ( - - - + return duplicated ? ( + + +
+ setDropdownOpen(false)} + open={dropdownOpen} + overlay={ + + {props.renderDuplicationPopup(index, line.line)} + + }>
{ + setDropdownOpen(true); + if (!blocksLoaded && line.duplicated && props.onClick) { + props.onClick(line); + } + }} role="button" tabIndex={0} /> - - - - ) : ( - -
- - ); - } + +
+ + + ) : ( + +
+ + ); } + +export default React.memo(LineDuplicationBlock); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplications.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplications.tsx deleted file mode 100644 index 4cffc133392..00000000000 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplications.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 classNames from 'classnames'; -import * as React from 'react'; -import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; -import { translate } from 'sonar-ui-common/helpers/l10n'; - -interface Props { - line: T.SourceLine; - onClick: (line: T.SourceLine) => void; -} - -export default class LineDuplications extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - this.props.onClick(this.props.line); - }; - - render() { - const { line } = this.props; - const className = classNames('source-meta', 'source-line-duplications', { - 'source-line-duplicated': line.duplicated - }); - - const cell = ( - -
- - ); - - return line.duplicated ? ( - - {cell} - - ) : ( - cell - ); - } -} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx index 9a86486ff74..534e73a5619 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx @@ -20,38 +20,44 @@ import * as classNames from 'classnames'; import * as React from 'react'; import IssueIcon from 'sonar-ui-common/components/icons/IssueIcon'; +import { translate } from 'sonar-ui-common/helpers/l10n'; import { sortByType } from '../../../helpers/issues'; -interface Props { +export interface LineIssuesIndicatorProps { issues: T.Issue[]; + issuesOpen?: boolean; line: T.SourceLine; onClick: () => void; } -export default class LineIssuesIndicator extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - this.props.onClick(); - }; +export function LineIssuesIndicator(props: LineIssuesIndicatorProps) { + const { issues, issuesOpen, line } = props; + const hasIssues = issues.length > 0; + const className = classNames('source-meta', 'source-line-issues', { + 'source-line-with-issues': hasIssues + }); + const mostImportantIssue = hasIssues ? sortByType(issues)[0] : null; - render() { - const { issues, line } = this.props; - const hasIssues = issues.length > 0; - const className = classNames('source-meta', 'source-line-issues', { - 'source-line-with-issues': hasIssues - }); - const mostImportantIssue = hasIssues ? sortByType(issues)[0] : null; + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.currentTarget.blur(); + props.onClick(); + }; - return ( - - {mostImportantIssue != null && } - {issues.length > 1 && {issues.length}} - - ); - } + return ( + + {hasIssues && ( + + {mostImportantIssue != null && } + {issues.length > 1 && {issues.length}} + + )} + + ); } + +export default React.memo(LineIssuesIndicator); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx index 1b9de7e2c65..6b59a02dd2c 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx @@ -18,51 +18,33 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Toggler from 'sonar-ui-common/components/controls/Toggler'; +import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; +import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; +import { translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import LineOptionsPopup from './LineOptionsPopup'; -interface Props { +export interface LineNumberProps { line: T.SourceLine; - onPopupToggle: (linePopup: T.LinePopup) => void; - popupOpen: boolean; } -export default class LineNumber extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - event.currentTarget.blur(); - this.props.onPopupToggle({ line: this.props.line.line, name: 'line-number' }); - }; - - handleTogglePopup = (open: boolean) => { - this.props.onPopupToggle({ line: this.props.line.line, name: 'line-number', open }); - }; - - closePopup = () => { - this.handleTogglePopup(false); - }; - - render() { - const { line, popupOpen } = this.props; - const { line: lineNumber } = line; - const hasLineNumber = !!lineNumber; - return hasLineNumber ? ( - - } - /> - - ) : ( - - ); - } +export function LineNumber({ line }: LineNumberProps) { + const { line: lineNumber } = line; + const hasLineNumber = !!lineNumber; + return hasLineNumber ? ( + + } + overlayPlacement={PopupPlacement.RightTop}> + + {lineNumber} + + + + ) : ( + + ); } + +export default React.memo(LineNumber); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx index 4bb7abd5733..2665c73b044 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx @@ -19,35 +19,30 @@ */ import * as React from 'react'; import { Link } from 'react-router'; -import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; -import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { getCodeUrl } from '../../../helpers/urls'; import { SourceViewerContext } from '../SourceViewerContext'; -interface Props { +interface LineOptionsPopupProps { line: T.SourceLine; } -export default function LineOptionsPopup({ line }: Props) { +export function LineOptionsPopup({ line }: LineOptionsPopupProps) { return ( {({ branchLike, file }) => ( - -
- { - event.stopPropagation(); - }} - rel="noopener noreferrer" - target="_blank" - to={getCodeUrl(file.project, branchLike, file.key, line.line)}> - {translate('component_viewer.get_permalink')} - -
-
+
+ + {translate('component_viewer.get_permalink')} + +
)}
); } + +export default React.memo(LineOptionsPopup); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx index 5d43f7719c3..b837748e804 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx @@ -18,54 +18,44 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Toggler from 'sonar-ui-common/components/controls/Toggler'; +import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; +import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; +import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import SCMPopup from './SCMPopup'; -interface Props { +export interface LineSCMProps { line: T.SourceLine; - onPopupToggle: (linePopup: T.LinePopup) => void; - popupOpen: boolean; previousLine: T.SourceLine | undefined; } -export default class LineSCM extends React.PureComponent { - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - event.currentTarget.blur(); - this.props.onPopupToggle({ line: this.props.line.line, name: 'scm' }); - }; +export function LineSCM({ line, previousLine }: LineSCMProps) { + const hasPopup = !!line.line; + const cell = ( +
+ {isSCMChanged(line, previousLine) ? line.scmAuthor || '…' : ' '} +
+ ); - handleTogglePopup = (open: boolean) => { - this.props.onPopupToggle({ line: this.props.line.line, name: 'scm', open }); - }; + if (hasPopup) { + let ariaLabel = translate('source_viewer.click_for_scm_info'); + if (line.scmAuthor) { + ariaLabel = `${translateWithParameters( + 'source_viewer.author_X', + line.scmAuthor + )}, ${ariaLabel}`; + } - closePopup = () => { - this.handleTogglePopup(false); - }; - - render() { - const { line, popupOpen, previousLine } = this.props; - const hasPopup = !!line.line; - const cell = isSCMChanged(line, previousLine) && ( -
- ); - return hasPopup ? ( - - }> - {cell} - + return ( + + } overlayPlacement={PopupPlacement.RightTop}> +
+ {cell} +
+
- ) : ( + ); + } else { + return ( {cell} @@ -80,3 +70,5 @@ function isSCMChanged(s: T.SourceLine, p: T.SourceLine | undefined) { } return changed; } + +export default React.memo(LineSCM); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/SCMPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/SCMPopup.tsx index 7f66c5963bd..408a331f07e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/SCMPopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/SCMPopup.tsx @@ -19,27 +19,38 @@ */ import * as classNames from 'classnames'; import * as React from 'react'; -import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; -import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; +import { translate } from 'sonar-ui-common/helpers/l10n'; -interface Props { +export interface SCMPopupProps { line: T.SourceLine; } -export default function SCMPopup({ line }: Props) { - const hasAuthor = line.scmAuthor !== ''; +export function SCMPopup({ line }: SCMPopupProps) { + const hasAuthor = line.scmAuthor !== undefined && line.scmAuthor !== ''; + const hasDate = line.scmDate !== undefined; return ( - -
- {hasAuthor &&
{line.scmAuthor}
} - {line.scmDate && ( -
- -
- )} - {line.scmRevision &&
{line.scmRevision}
} -
-
+
+ {hasAuthor && ( +
+

{translate('author')}

+ {line.scmAuthor} +
+ )} + {hasDate && ( +
+

{translate('source_viewer.tooltip.scm.commited_on')}

+ +
+ )} + {line.scmRevision && ( +
+

{translate('source_viewer.tooltip.scm.revision')}

+ {line.scmRevision} +
+ )} +
); } + +export default React.memo(SCMPopup); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx index 4bff33578fc..6fbe865517e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx @@ -106,9 +106,7 @@ function shallowRender(props: Partial = {}) { 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()} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx index 446700629b6..a458bc3fbea 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx @@ -19,22 +19,24 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import LineCoverage from '../LineCoverage'; +import { LineCoverage, LineCoverageProps } from '../LineCoverage'; -it('render covered line', () => { - const line: T.SourceLine = { line: 3, coverageStatus: 'covered' }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('covered'); + expect(shallowRender({ line: { line: 3, coverageStatus: 'uncovered' } })).toMatchSnapshot( + 'uncovered' + ); + expect(shallowRender({ line: { line: 3, coverageStatus: 'partially-covered' } })).toMatchSnapshot( + 'partially covered, 0 conditions' + ); + expect( + shallowRender({ line: { line: 3, coverageStatus: 'partially-covered', coveredConditions: 10 } }) + ).toMatchSnapshot('partially covered, 10 conditions'); + expect(shallowRender({ line: { line: 3, coverageStatus: undefined } })).toMatchSnapshot( + 'no data' + ); }); -it('render uncovered line', () => { - const line: T.SourceLine = { line: 3, coverageStatus: 'uncovered' }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); -}); - -it('render line with unknown coverage', () => { - const line: T.SourceLine = { line: 3 }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); -}); +function shallowRender(props: Partial = {}) { + return shallow(); +} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplicationBlock-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplicationBlock-test.tsx index 325c027d988..b837d1a429a 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplicationBlock-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplicationBlock-test.tsx @@ -19,38 +19,50 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import Toggler from 'sonar-ui-common/components/controls/Toggler'; import { click } from 'sonar-ui-common/helpers/testUtils'; -import LineDuplicationBlock from '../LineDuplicationBlock'; +import { LineDuplicationBlock, LineDuplicationBlockProps } from '../LineDuplicationBlock'; -it('render duplicated line', () => { - const line = { line: 3, duplicated: true }; - const onPopupToggle = jest.fn(); - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - click(wrapper.find('[tabIndex]')); - expect(onPopupToggle).toHaveBeenCalled(); +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect( + shallowRender({ line: { line: 3, duplicated: false }, duplicated: false }) + ).toMatchSnapshot('not duplicated'); +}); + +it('should correctly open/close the dropdown', () => { + const wrapper = shallowRender(); + click(wrapper.find('div[role="button"]')); + expect(wrapper.find(Toggler).prop('open')).toBe(true); + wrapper.find(Toggler).prop('onRequestClose')(); + expect(wrapper.find(Toggler).prop('open')).toBe(false); }); -it('render not duplicated line', () => { - const line = { line: 3, duplicated: false }; - const wrapper = shallow( +it('should correctly call the onCick prop', () => { + const line = { line: 1, duplicated: true }; + const onClick = jest.fn(); + const wrapper = shallowRender({ line, onClick }); + + // Propagate if blocks aren't loaded. + click(wrapper.find('div[role="button"]')); + expect(onClick).toBeCalledWith(line); + + // Don't propagate if blocks were loaded. + onClick.mockClear(); + wrapper.setProps({ blocksLoaded: true }); + click(wrapper.find('div[role="button"]')); + expect(onClick).not.toBeCalled(); +}); + +function shallowRender(props: Partial = {}) { + return shallow( ); - expect(wrapper).toMatchSnapshot(); -}); +} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplications-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplications-test.tsx deleted file mode 100644 index 1a52277d12a..00000000000 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplications-test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { click } from 'sonar-ui-common/helpers/testUtils'; -import LineDuplications from '../LineDuplications'; - -it('render duplicated line', () => { - const line = { line: 3, duplicated: true }; - const onClick = jest.fn(); - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - click(wrapper.find('[tabIndex]')); - expect(onClick).toHaveBeenCalled(); -}); - -it('render not duplicated line', () => { - const line = { line: 3, duplicated: false }; - const onClick = jest.fn(); - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx index 9d83642e2bd..955d74d72e9 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx @@ -21,29 +21,30 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { click } from 'sonar-ui-common/helpers/testUtils'; import { mockIssue } from '../../../../helpers/testMocks'; -import LineIssuesIndicator from '../LineIssuesIndicator'; +import { LineIssuesIndicator, LineIssuesIndicatorProps } from '../LineIssuesIndicator'; it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect( + shallowRender({ + issues: [ + mockIssue(false, { key: 'foo', type: 'VULNERABILITY' }), + mockIssue(false, { key: 'bar', type: 'SECURITY_HOTSPOT' }) + ] + }) + ).toMatchSnapshot('diff issue types'); + expect(shallowRender({ issues: [] })).toMatchSnapshot('no issues'); +}); + +it('should correctly handle click', () => { const onClick = jest.fn(); const wrapper = shallowRender({ onClick }); - expect(wrapper).toMatchSnapshot(); - click(wrapper); + click(wrapper.find('span[role="button"]')); expect(onClick).toHaveBeenCalled(); - - const nextIssues = [ - mockIssue(false, { key: 'foo', type: 'VULNERABILITY' }), - mockIssue(false, { key: 'bar', type: 'SECURITY_HOTSPOT' }) - ]; - wrapper.setProps({ issues: nextIssues }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render correctly for no issues', () => { - expect(shallowRender({ issues: [] })).toMatchSnapshot(); }); -function shallowRender(props: Partial = {}) { +function shallowRender(props: Partial = {}) { return shallow( { - const line = { line: 3 }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); - click(wrapper); +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect(shallowRender({ line: { line: 0 } })).toMatchSnapshot('no line number'); }); -it('render line 0', () => { - const line = { line: 0 }; - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); -}); +function shallowRender(props: Partial = {}) { + return shallow(); +} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx index be4112fa40c..c6fae4d5176 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx @@ -20,7 +20,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { mockBranch } from '../../../../helpers/mocks/branch-like'; -import LineOptionsPopup from '../LineOptionsPopup'; +import { LineOptionsPopup } from '../LineOptionsPopup'; jest.mock('../../SourceViewerContext', () => ({ SourceViewerContext: { @@ -32,7 +32,7 @@ jest.mock('../../SourceViewerContext', () => ({ } })); -it('should render', () => { +it('should render correctly', () => { const line = { line: 3 }; const wrapper = shallow().dive(); expect(wrapper).toMatchSnapshot(); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineSCM-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineSCM-test.tsx index fa007db2e62..bd6fce7f69f 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineSCM-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineSCM-test.tsx @@ -17,52 +17,27 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* eslint-disable sonarjs/no-duplicate-string */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { click } from 'sonar-ui-common/helpers/testUtils'; -import LineSCM from '../LineSCM'; +import { LineSCM, LineSCMProps } from '../LineSCM'; -it('render scm details', () => { - const line = { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' }; - const previousLine = { line: 2, scmAuthor: 'bar', scmDate: '2017-01-02' }; - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('render scm details for the first line', () => { - const line = { line: 3, scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); -}); +it('should render correctly', () => { + const scmInfo = { scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; -it('does not render scm details', () => { - const line = { line: 3, scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; - const previousLine = { line: 2, scmRevision: 'foo', scmAuthor: 'foo', scmDate: '2017-01-01' }; - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot('default'); + expect( + shallowRender({ line: { line: 3, ...scmInfo }, previousLine: { line: 2, ...scmInfo } }) + ).toMatchSnapshot('same commit'); + expect(shallowRender({ line: { line: 3, scmDate: '2017-01-01' } })).toMatchSnapshot('no author'); }); -it('renders ellipsis when no author info', () => { - const line = { line: 3, scmRevision: 'foo', scmDate: '2017-01-01' }; - const previousLine = { line: 2, scmRevision: 'bar', scmDate: '2017-01-01' }; - const wrapper = shallow( - +function shallowRender(props: Partial = {}) { + return shallow( + ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should open popup', () => { - const line = { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' }; - const onPopupToggle = jest.fn(); - const wrapper = shallow( - - ); - click(wrapper.find('[role="button"]')); - expect(onPopupToggle).toBeCalledWith({ line: 3, name: 'scm' }); -}); +} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/SCMPopup-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/SCMPopup-test.tsx index f68afd655ad..69fe6f4bdf5 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/SCMPopup-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/SCMPopup-test.tsx @@ -17,11 +17,29 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* eslint-disable sonarjs/no-duplicate-string */ import { shallow } from 'enzyme'; import * as React from 'react'; -import SCMPopup from '../SCMPopup'; +import { SCMPopup, SCMPopupProps } from '../SCMPopup'; -it('should render', () => { - const line = { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' }; - expect(shallow()).toMatchSnapshot(); +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot('default'); + expect( + shallowRender({ line: { line: 3, scmDate: '2017-01-01', scmRevision: 'bar' } }) + ).toMatchSnapshot('no author'); + expect( + shallowRender({ line: { line: 3, scmAuthor: 'foo', scmRevision: 'bar' } }) + ).toMatchSnapshot('no date'); + expect( + shallowRender({ line: { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' } }) + ).toMatchSnapshot('no revision'); }); + +function shallowRender(props: Partial = {}) { + return shallow( + + ); +} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap index 931708c4071..864377427da 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap @@ -5,7 +5,7 @@ exports[`should render correctly 1`] = ` className="source-line source-line-filtered" data-line-number={16} > - import java.util.ArrayList;", @@ -19,10 +19,8 @@ exports[`should render correctly 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -36,8 +34,6 @@ exports[`should render correctly 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -165,10 +161,8 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -182,8 +176,6 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -311,10 +303,8 @@ exports[`should render correctly with coverage 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -328,13 +318,11 @@ exports[`should render correctly with coverage 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -458,7 +446,7 @@ exports[`should render correctly with duplication information 1`] = ` className="source-line source-line-filtered" data-line-number={16} > - import java.util.ArrayList;", @@ -472,10 +460,8 @@ exports[`should render correctly with duplication information 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -489,29 +475,12 @@ exports[`should render correctly with duplication information 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", - "coverageStatus": "covered", - "coveredConditions": 2, - "duplicated": false, - "isNew": true, - "line": 16, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - onClick={[MockFunction]} - /> - - - - import java.util.ArrayList;", @@ -697,10 +663,8 @@ exports[`should render correctly with issues info 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -714,10 +678,8 @@ exports[`should render correctly with issues info 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} /> - import java.util.ArrayList;", @@ -908,7 +871,7 @@ exports[`should render correctly: no SCM 1`] = ` className="source-line source-line-filtered" data-line-number={16} > - import java.util.ArrayList;", @@ -922,8 +885,6 @@ exports[`should render correctly: no SCM 1`] = ` "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", } } - onPopupToggle={[MockFunction]} - popupOpen={false} />
`; -exports[`render line with unknown coverage 1`] = ` +exports[`should render correctly: no data 1`] = ` `; -exports[`render uncovered line 1`] = ` +exports[`should render correctly: partially covered, 0 conditions 1`] = ` + + +
+ + +`; + +exports[`should render correctly: partially covered, 10 conditions 1`] = ` + + +
+ + +`; + +exports[`should render correctly: uncovered 1`] = `
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplicationBlock-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplicationBlock-test.tsx.snap index 86a35b7a083..2be051a9d1d 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplicationBlock-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplicationBlock-test.tsx.snap @@ -1,33 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`render duplicated line 1`] = ` +exports[`should render correctly: default 1`] = ` - - -
- - +
+ + } + > +
+ +
+ `; -exports[`render not duplicated line 1`] = ` +exports[`should render correctly: not duplicated 1`] = ` diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplications-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplications-test.tsx.snap deleted file mode 100644 index d40e9e6d111..00000000000 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplications-test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render duplicated line 1`] = ` - - -
- - -`; - -exports[`render not duplicated line 1`] = ` - -
- -`; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap index fbb8d6eb661..160ac74b311 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap @@ -1,44 +1,52 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render correctly 1`] = ` +exports[`should render correctly: default 1`] = ` - - 2 + + + 2 + `; -exports[`should render correctly 2`] = ` +exports[`should render correctly: diff issue types 1`] = ` - - 2 + + + 2 + `; -exports[`should render correctly for no issues 1`] = ` +exports[`should render correctly: no issues 1`] = ` -`; - -exports[`render line 3 1`] = ` +exports[`should render correctly: default 1`] = ` - } - /> + overlayPlacement="right-top" + > + + 3 + + `; + +exports[`should render correctly: no line number 1`] = ` + +`; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap index 936909da9dc..d0c730eba89 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap @@ -1,33 +1,28 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render 1`] = ` - -
- - component_viewer.get_permalink - -
-
+ } + > + component_viewer.get_permalink + +
`; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineSCM-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineSCM-test.tsx.snap index b72bebfac30..87b8700d5e0 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineSCM-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineSCM-test.tsx.snap @@ -1,45 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`does not render scm details 1`] = ` +exports[`should render correctly: default 1`] = ` - - } - /> - -`; - -exports[`render scm details 1`] = ` - - } + overlayPlacement="right-top" >
- + aria-label="source_viewer.author_X.foo, source_viewer.click_for_scm_info" + role="button" + > +
+ foo +
+
+ `; -exports[`render scm details for the first line 1`] = ` +exports[`should render correctly: no author 1`] = ` - } + overlayPlacement="right-top" >
- + aria-label="source_viewer.click_for_scm_info" + role="button" + > +
+ … +
+
+ `; -exports[`renders ellipsis when no author info 1`] = ` +exports[`should render correctly: same commit 1`] = ` - } + overlayPlacement="right-top" >
- + aria-label="source_viewer.author_X.foo, source_viewer.click_for_scm_info" + role="button" + > +
+ +
+
+ `; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/SCMPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/SCMPopup-test.tsx.snap index 99378cb457d..a05f410a191 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/SCMPopup-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/SCMPopup-test.tsx.snap @@ -1,22 +1,101 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render 1`] = ` - +
+

+ author +

+ foo +
+
+

+ source_viewer.tooltip.scm.commited_on +

+ +
+
+

+ source_viewer.tooltip.scm.revision +

+ bar +
+
+`; + +exports[`should render correctly: no author 1`] = ` +
+
+

+ source_viewer.tooltip.scm.commited_on +

+ +
+
+

+ source_viewer.tooltip.scm.revision +

+ bar +
+
+`; + +exports[`should render correctly: no date 1`] = ` +
+
+

+ author +

+ foo +
+
+

+ source_viewer.tooltip.scm.revision +

+ bar +
+
+`; + +exports[`should render correctly: no revision 1`] = ` +
+
+

+ author +

+ foo +
-
- foo -
-
- -
-
- +

+ source_viewer.tooltip.scm.commited_on +

+ +
+
`; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 1a059e1664a..ac445523a14 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2488,6 +2488,9 @@ source_viewer.view_all_issues=See all issues in this file source_viewer.covered=Covered by the following tests source_viewer.not_covered=Not covered by tests source_viewer.conditions=conditions +source_viewer.line_X=Line: {0} +source_viewer.click_for_scm_info=Click to see SCM information +source_viewer.author_X=Author: {0} source_viewer.tooltip.duplicated_line=This line is duplicated. Click to see duplicated blocks. source_viewer.tooltip.duplicated_block=Duplicated block. Click for details. @@ -2498,6 +2501,11 @@ source_viewer.tooltip.partially-covered.conditions=Partially covered by tests ({ source_viewer.tooltip.uncovered=Not covered by tests. source_viewer.tooltip.uncovered.conditions=Not covered by tests ({0} conditions). source_viewer.tooltip.no_information_about_tests=There is no extra information about test files. +source_viewer.tooltip.scm.commited_on=Committed on +source_viewer.tooltip.scm.revision=Revision + +source_viewer.issues_on_line.show=Click to show all issues on this line +source_viewer.issues_on_line.hide=Click to hide all issues on this line source_viewer.load_more_code=Load More Code source_viewer.loading_more_code=Loading More Code... -- 2.39.5