Kaynağa Gözat

SONAR-12911 Code viewer is not accessible to blind users

tags/8.4.0.35506
Wouter Admiraal 4 yıl önce
ebeveyn
işleme
ae779528b3
47 değiştirilmiş dosya ile 752 ekleme ve 1071 silme
  1. 4
    25
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx
  2. 7
    49
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx
  3. 7
    9
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx
  4. 0
    20
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx
  5. 0
    27
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewerWrapper-test.tsx
  6. 0
    4
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx
  7. 1
    1
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap
  8. 0
    17
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap
  9. 1
    22
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx
  10. 0
    6
      server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx
  11. 0
    26
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx
  12. 0
    2
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx
  13. 0
    1
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap
  14. 0
    2
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
  15. 5
    43
      server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx
  16. 0
    4
      server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx
  17. 0
    1
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap
  18. 59
    65
      server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx
  19. 11
    23
      server/sonar-web/src/main/js/components/SourceViewer/components/Line.css
  20. 59
    57
      server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx
  21. 8
    4
      server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx
  22. 43
    51
      server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx
  23. 0
    60
      server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplications.tsx
  24. 31
    25
      server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx
  25. 24
    42
      server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx
  26. 13
    18
      server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx
  27. 31
    39
      server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx
  28. 27
    16
      server/sonar-web/src/main/js/components/SourceViewer/components/SCMPopup.tsx
  29. 0
    2
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
  30. 18
    16
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx
  31. 38
    26
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplicationBlock-test.tsx
  32. 0
    39
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplications-test.tsx
  33. 16
    15
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx
  34. 7
    12
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx
  35. 2
    2
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx
  36. 17
    42
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineSCM-test.tsx
  37. 22
    4
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/SCMPopup-test.tsx
  38. 21
    60
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap
  39. 39
    3
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap
  40. 27
    19
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplicationBlock-test.tsx.snap
  41. 0
    29
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplications-test.tsx.snap
  42. 27
    19
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap
  43. 18
    15
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap
  44. 23
    28
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap
  45. 43
    65
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineSCM-test.tsx.snap
  46. 95
    16
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/SCMPopup-test.tsx.snap
  47. 8
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 4
- 25
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx Dosyayı Görüntüle

@@ -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
<SnippetViewer
branchLike={this.props.branchLike}
component={this.props.snippetGroup.component}
duplications={this.props.duplications}
duplicationsByLine={this.props.duplicationsByLine}
expandBlock={this.expandBlock}
handleCloseIssues={this.handleCloseIssues}
handleLinePopupToggle={this.handleLinePopupToggle}
handleOpenIssues={this.handleOpenIssues}
handleSymbolClick={this.handleSymbolClick}
highlightedLocationMessage={this.props.highlightedLocationMessage}
@@ -338,7 +329,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone
issuePopup={this.props.issuePopup}
issuesByLine={issuesByLine}
lastSnippetOfLastGroup={lastSnippetOfLastGroup}
linePopup={this.props.linePopup}
loadDuplications={this.loadDuplications}
locations={this.props.locations}
locationsByLine={locationsByLine}
@@ -354,14 +344,7 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone
}

render() {
const {
branchLike,
duplications,
issue,
issuesByLine,
lastSnippetGroup,
snippetGroup
} = this.props;
const { branchLike, issue, issuesByLine, lastSnippetGroup, snippetGroup } = this.props;
const { additionalLines, loading, snippets } = this.state;
const locations =
issue.component === snippetGroup.component.key ? locationsByLine([issue]) : {};
@@ -382,11 +365,7 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone
isFlow ? lastSnippetGroup && snippetIndex === snippets.length - 1 : snippetIndex === 0;

return (
<div
className={classNames('component-source-container', {
'source-duplications-expanded': duplications && duplications.length > 0
})}
ref={this.rootNodeRef}>
<div className="component-source-container" ref={this.rootNodeRef}>
<SourceViewerHeaderSlim
branchLike={branchLike}
expandable={!fullyShown}

+ 7
- 49
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx Dosyayı Görüntüle

@@ -60,7 +60,6 @@ interface State {
duplications?: T.Duplication[];
duplicationsByLine: { [line: number]: number[] };
issuePopup?: { issue: string; name: string };
linePopup?: T.LinePopup & { component: string };
loading: boolean;
notAccessible: boolean;
}
@@ -89,22 +88,18 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone
this.mounted = false;
}

fetchDuplications = (component: string, line: T.SourceLine) => {
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 (
<div>
{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 (
<SourceViewerContext.Provider
// eslint-disable-next-line react/no-array-index-key
@@ -246,6 +204,8 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone
value={{ branchLike: this.props.branchLike, file: snippetGroup.component }}>
<ComponentSourceSnippetGroupViewer
branchLike={this.props.branchLike}
duplications={duplications}
duplicationsByLine={duplicationsByLine}
highlightedLocationMessage={this.props.highlightedLocationMessage}
issue={issue}
issuePopup={this.state.issuePopup}
@@ -255,12 +215,10 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone
locations={snippetGroup.locations || []}
onIssueChange={this.props.onIssueChange}
onIssuePopupToggle={this.handleIssuePopupToggle}
onLinePopupToggle={this.handleLinePopupToggle}
onLocationSelect={this.props.onLocationSelect}
renderDuplicationPopup={this.renderDuplicationPopup}
scroll={this.props.scroll}
snippetGroup={snippetGroup}
{...componentProps}
/>
</SourceViewerContext.Provider>
);

+ 7
- 9
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx Dosyayı Görüntüle

@@ -42,7 +42,6 @@ interface Props {
duplicationsByLine?: { [line: number]: number[] };
expandBlock: (snippetIndex: number, direction: T.ExpandDirection) => Promise<void>;
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<Props> {
(duplicationsCount && duplicationsByLine && duplicationsByLine[line.line]) || [];

const isSinkLine = issuesForLine.some(i => i.key === this.props.issue.key);
const noop = () => {};

return (
<Line
@@ -162,15 +161,13 @@ export default class SnippetViewer extends React.PureComponent<Props> {
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<Props> {
? 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 (
<div className="source-viewer-code snippet" ref={this.snippetNodeRef}>

+ 0
- 20
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx Dosyayı Görüntüle

@@ -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<Function>('loadDuplications')(line);
expect(loadDuplications).toHaveBeenCalledWith('a', line);

wrapper
.find('SnippetViewer')
.first()
.prop<Function>('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<ComponentSourceSnippetGroupViewer>(
<ComponentSourceSnippetGroupViewer
branchLike={mockMainBranch()}
duplications={undefined}
duplicationsByLine={undefined}
highlightedLocationMessage={{ index: 0, text: '' }}
issue={mockIssue()}
issuesByLine={{}}
lastSnippetGroup={false}
linePopup={undefined}
loadDuplications={jest.fn()}
locations={[]}
onIssueChange={jest.fn()}
onIssuePopupToggle={jest.fn()}
onLinePopupToggle={jest.fn()}
onLocationSelect={jest.fn()}
renderDuplicationPopup={jest.fn()}
scroll={jest.fn()}
@@ -322,18 +310,14 @@ describe('getHeight', () => {
const wrapper = mount<ComponentSourceSnippetGroupViewer>(
<ComponentSourceSnippetGroupViewer
branchLike={mockMainBranch()}
duplications={undefined}
duplicationsByLine={undefined}
highlightedLocationMessage={{ index: 0, text: '' }}
issue={mockIssue()}
issuesByLine={{}}
lastSnippetGroup={false}
linePopup={undefined}
loadDuplications={jest.fn()}
locations={[]}
onIssueChange={jest.fn()}
onIssuePopupToggle={jest.fn()}
onLinePopupToggle={jest.fn()}
onLocationSelect={jest.fn()}
renderDuplicationPopup={jest.fn()}
scroll={jest.fn()}
@@ -376,18 +360,14 @@ function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']
return shallow<ComponentSourceSnippetGroupViewer>(
<ComponentSourceSnippetGroupViewer
branchLike={mockMainBranch()}
duplications={undefined}
duplicationsByLine={undefined}
highlightedLocationMessage={{ index: 0, text: '' }}
issue={mockIssue()}
issuesByLine={{}}
lastSnippetGroup={false}
linePopup={undefined}
loadDuplications={jest.fn()}
locations={[]}
onIssueChange={jest.fn()}
onIssuePopupToggle={jest.fn()}
onLinePopupToggle={jest.fn()}
onLocationSelect={jest.fn()}
renderDuplicationPopup={jest.fn()}
scroll={jest.fn()}

+ 0
- 27
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewerWrapper-test.tsx Dosyayı Görüntüle

@@ -87,27 +87,6 @@ it('should handle issue popup', () => {
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<Function>('onLinePopupToggle')(linePopup);
expect(wrapper.state('linePopup')).toEqual(linePopup);

wrapper.find('ComponentSourceSnippetGroupViewer').prop<Function>('onLinePopupToggle')(linePopup);
expect(wrapper.state('linePopup')).toBeUndefined();

const openLinePopup = { ...linePopup, open: true };
wrapper.find('ComponentSourceSnippetGroupViewer').prop<Function>('onLinePopupToggle')(
openLinePopup
);
wrapper.find('ComponentSourceSnippetGroupViewer').prop<Function>('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<Function>('renderDuplicationPopup')(

+ 0
- 4
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx Dosyayı Görüntüle

@@ -127,7 +127,6 @@ function shallowRender(props: Partial<SnippetViewer['props']> = {}) {
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<SnippetViewer['props']> = {}) {
issue={mockIssue()}
issuesByLine={{}}
lastSnippetOfLastGroup={false}
linePopup={undefined}
loadDuplications={jest.fn()}
locations={[]}
locationsByLine={{}}
@@ -161,7 +159,6 @@ function mountRender(props: Partial<SnippetViewer['props']> = {}) {
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<SnippetViewer['props']> = {}) {
issue={mockIssue()}
issuesByLine={{}}
lastSnippetOfLastGroup={false}
linePopup={undefined}
loadDuplications={jest.fn()}
locations={[]}
locationsByLine={{}}

+ 1
- 1
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap Dosyayı Görüntüle

@@ -48,6 +48,7 @@ exports[`should render correctly 2`] = `
}
>
<ComponentSourceSnippetGroupViewer
duplicationsByLine={Object {}}
issue={
Object {
"actions": Array [],
@@ -169,7 +170,6 @@ exports[`should render correctly 2`] = `
}
onIssueChange={[MockFunction]}
onIssuePopupToggle={[Function]}
onLinePopupToggle={[Function]}
onLocationSelect={[MockFunction]}
renderDuplicationPopup={[Function]}
scroll={[MockFunction]}

+ 0
- 17
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap Dosyayı Görüntüle

@@ -61,7 +61,6 @@ exports[`should render correctly 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
renderDuplicationPopup={[MockFunction]}
@@ -110,7 +109,6 @@ exports[`should render correctly 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -172,7 +170,6 @@ exports[`should render correctly 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -271,7 +268,6 @@ exports[`should render correctly when at the bottom of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
renderDuplicationPopup={[MockFunction]}
@@ -320,7 +316,6 @@ exports[`should render correctly when at the bottom of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -382,7 +377,6 @@ exports[`should render correctly when at the bottom of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -444,7 +438,6 @@ exports[`should render correctly when at the bottom of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -532,7 +525,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
renderDuplicationPopup={[MockFunction]}
@@ -581,7 +573,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -643,7 +634,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -705,7 +695,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -767,7 +756,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -829,7 +817,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -891,7 +878,6 @@ exports[`should render correctly when at the top of the file 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -991,7 +977,6 @@ exports[`should render correctly with no SCM 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
renderDuplicationPopup={[MockFunction]}
@@ -1041,7 +1026,6 @@ exports[`should render correctly with no SCM 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={
@@ -1104,7 +1088,6 @@ exports[`should render correctly with no SCM 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[MockFunction]}
onIssuesOpen={[MockFunction]}
onLinePopupToggle={[MockFunction]}
onLocationSelect={[MockFunction]}
onSymbolClick={[MockFunction]}
previousLine={

+ 1
- 22
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainer.tsx Dosyayı Görüntüle

@@ -37,7 +37,6 @@ interface State {
highlightedSymbols: string[];
lastLine?: number;
loading: boolean;
linePopup?: T.LinePopup & { component: string };
sourceLines: T.SourceLine[];
}

@@ -143,31 +142,13 @@ export default class HotspotSnippetContainer extends React.Component<Props, Stat
});
};

handleLinePopupToggle = (params: T.LinePopup & { component: string }) => {
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<Props, Stat
highlightedSymbols={highlightedSymbols}
hotspot={hotspot}
lastLine={lastLine}
linePopup={linePopup}
loading={loading}
locations={locations}
onExpandBlock={this.handleExpansion}
onLinePopupToggle={this.handleLinePopupToggle}
onSymbolClick={this.handleSymbolClick}
sourceLines={sourceLines}
sourceViewerFile={sourceViewerFile}

+ 0
- 6
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx Dosyayı Görüntüle

@@ -33,9 +33,7 @@ export interface HotspotSnippetContainerRendererProps {
lastLine?: number;
loading: boolean;
locations: { [line: number]: T.LinearIssueLocation[] };
linePopup?: T.LinePopup & { component: string };
onExpandBlock: (direction: T.ExpandDirection) => Promise<void>;
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}

+ 0
- 26
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx Dosyayı Görüntüle

@@ -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'];

+ 0
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx Dosyayı Görüntüle

@@ -39,11 +39,9 @@ function shallowRender(props?: Partial<HotspotSnippetContainerRendererProps>) {
highlightedSymbols={[]}
hotspot={mockHotspot()}
lastLine={undefined}
linePopup={undefined}
loading={false}
locations={{}}
onExpandBlock={jest.fn()}
onLinePopupToggle={jest.fn()}
onSymbolClick={jest.fn()}
sourceLines={[]}
sourceViewerFile={mockSourceViewerFile()}

+ 0
- 1
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainer-test.tsx.snap Dosyayı Görüntüle

@@ -124,7 +124,6 @@ exports[`should render correctly 1`] = `
}
}
onExpandBlock={[Function]}
onLinePopupToggle={[Function]}
onSymbolClick={[Function]}
sourceLines={Array []}
sourceViewerFile={

+ 0
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap Dosyayı Görüntüle

@@ -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]}

+ 5
- 43
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx Dosyayı Görüntüle

@@ -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<T.DuplicatedFile>;
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<Props, State>
super(props);

this.state = {
displayDuplications: false,
duplicationsByLine: {},
hasSourcesAfter: false,
highlightedSymbols: [],
@@ -245,7 +241,6 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
this.setState(
{
component,
displayDuplications: false,
duplicatedFiles: undefined,
duplications: undefined,
duplicationsByLine: {},
@@ -254,7 +249,6 @@ export default class SourceViewerBase extends React.PureComponent<Props, State>
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<Props, State>
);
};

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<Props, State>
);
};

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<Props, State>
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<Props, State>
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<Props, State>
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<Props, State>
return null;
}

const className = classNames('source-viewer', {
'source-duplications-expanded': this.state.displayDuplications
});

return (
<SourceViewerContext.Provider value={{ branchLike: this.props.branchLike, file: component }}>
<div className={className} ref={node => (this.node = node)}>
<div className="source-viewer" ref={node => (this.node = node)}>
{this.renderHeader(this.props.branchLike, component)}
{sourceRemoved && (
<Alert className="spacer-top" variant="warning">

+ 0
- 4
server/sonar-web/src/main/js/components/SourceViewer/SourceViewerCode.tsx Dosyayı Görüntüle

@@ -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<Props> {
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<Props> {
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}

+ 0
- 1
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap Dosyayı Görüntüle

@@ -153,7 +153,6 @@ exports[`should render correctly 1`] = `
onIssueUnselect={[Function]}
onIssuesClose={[Function]}
onIssuesOpen={[Function]}
onLinePopupToggle={[Function]}
onSymbolClick={[Function]}
openIssuesByLine={Object {}}
renderDuplicationPopup={[Function]}

+ 59
- 65
server/sonar-web/src/main/js/components/SourceViewer/components/DuplicationPopup.tsx Dosyayı Görüntüle

@@ -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<T.DuplicatedFile>;
inRemovedComponent: boolean;
onClose: () => void;
openComponent: WorkspaceContextShape['openComponent'];
sourceViewerFile: T.SourceViewerFile;
}
@@ -65,7 +62,6 @@ export default class DuplicationPopup extends React.PureComponent<Props> {
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<Props> {
);

return (
<DropdownOverlay placement={PopupPlacement.RightTop}>
<div className="source-viewer-bubble-popup abs-width-400">
{this.props.inRemovedComponent && (
<Alert variant="warning">
{translate('duplications.dups_found_on_deleted_resource')}
</Alert>
)}
{duplications.length > 0 && (
<>
<h6 className="spacer-bottom">
{translate('component_viewer.transition.duplication')}
</h6>
{duplications.map(duplication => (
<div className="spacer-top text-ellipsis" key={duplication.file.key}>
<div className="component-name">
{this.isDifferentComponent(duplication.file, this.props.sourceViewerFile) && (
<>
<div className="source-viewer-bubble-popup abs-width-400">
{this.props.inRemovedComponent && (
<Alert variant="warning">
{translate('duplications.dups_found_on_deleted_resource')}
</Alert>
)}
{duplications.length > 0 && (
<>
<h6 className="spacer-bottom">
{translate('component_viewer.transition.duplication')}
</h6>
{duplications.map(duplication => (
<div className="spacer-top text-ellipsis" key={duplication.file.key}>
<div className="component-name">
{this.isDifferentComponent(duplication.file, this.props.sourceViewerFile) && (
<>
<div className="component-name-parent">
<QualifierIcon className="little-spacer-right" qualifier="TRK" />
<Link to={getProjectUrl(duplication.file.project)}>
{duplication.file.projectName}
</Link>
</div>
{duplication.file.subProject && duplication.file.subProjectName && (
<div className="component-name-parent">
<QualifierIcon className="little-spacer-right" qualifier="TRK" />
<Link to={getProjectUrl(duplication.file.project)}>
{duplication.file.projectName}
</Link>
<QualifierIcon className="little-spacer-right" qualifier="BRC" />
{duplication.file.subProjectName}
</div>
{duplication.file.subProject && duplication.file.subProjectName && (
<div className="component-name-parent">
<QualifierIcon className="little-spacer-right" qualifier="BRC" />
{duplication.file.subProjectName}
</div>
)}
</>
)}
)}
</>
)}

{duplication.file.key !== this.props.sourceViewerFile.key && (
<div className="component-name-path">
{this.renderDuplication(
duplication.file,
<>
<span>{collapsedDirFromPath(duplication.file.name)}</span>
<span className="component-name-file">
{fileFromPath(duplication.file.name)}
</span>
</>
)}
</div>
)}

{duplication.file.key !== this.props.sourceViewerFile.key && (
<div className="component-name-path">
<div className="component-name-path">
{'Lines: '}
{duplication.blocks.map((block, index) => (
<React.Fragment key={index}>
{this.renderDuplication(
duplication.file,
<>
<span>{collapsedDirFromPath(duplication.file.name)}</span>
<span className="component-name-file">
{fileFromPath(duplication.file.name)}
</span>
</>
{block.from}
{' – '}
{block.from + block.size - 1}
</>,
block.from
)}
</div>
)}

<div className="component-name-path">
{'Lines: '}
{duplication.blocks.map((block, index) => (
<React.Fragment key={index}>
{this.renderDuplication(
duplication.file,
<>
{block.from}
{' – '}
{block.from + block.size - 1}
</>,
block.from
)}
{index < duplication.blocks.length - 1 && ', '}
</React.Fragment>
))}
</div>
{index < duplication.blocks.length - 1 && ', '}
</React.Fragment>
))}
</div>
</div>
))}
</>
)}
</div>
</DropdownOverlay>
</div>
))}
</>
)}
</div>
);
}
}

+ 11
- 23
server/sonar-web/src/main/js/components/SourceViewer/components/Line.css Dosyayı Görüntüle

@@ -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;
}

+ 59
- 57
server/sonar-web/src/main/js/components/SourceViewer/components/Line.tsx Dosyayı Görüntüle

@@ -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<Props> {
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<Props> {

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 (
<tr className={className} data-line-number={line.line}>
<LineNumber
line={line}
onPopupToggle={this.props.onLinePopupToggle}
popupOpen={this.isPopupOpen('line-number')}
/>
<LineNumber line={line} />

{displaySCM && (
<LineSCM
line={line}
onPopupToggle={this.props.onLinePopupToggle}
popupOpen={this.isPopupOpen('scm')}
previousLine={this.props.previousLine}
/>
)}
{this.props.displayIssues && !this.props.displayAllIssues ? (
{displaySCM && <LineSCM line={line} previousLine={previousLine} />}
{displayIssues && !displayAllIssues ? (
<LineIssuesIndicator
issues={this.props.issues}
issues={issues}
issuesOpen={openIssues}
line={line}
onClick={this.handleIssuesIndicatorClick}
/>
@@ -145,34 +139,42 @@ export default class Line extends React.PureComponent<Props> {
<td className="source-meta source-line-issues" />
)}

{this.props.displayDuplications && (
<LineDuplications line={line} onClick={this.props.loadDuplications} />
)}

{times(duplicationsCount, index => (
{displayDuplications && (
<LineDuplicationBlock
duplicated={duplications.includes(index)}
index={index}
key={index}
blocksLoaded={duplicationsCount > 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 => (
<LineDuplicationBlock
blocksLoaded={true}
duplicated={duplications.includes(index + 1)}
index={index + 1}
key={index + 1}
line={this.props.line}
renderDuplicationPopup={this.props.renderDuplicationPopup}
/>
))}

{this.props.displayCoverage && <LineCoverage line={line} />}
{displayCoverage && <LineCoverage line={line} />}

<LineCode
branchLike={this.props.branchLike}
displayIssueLocationsCount={this.props.displayIssueLocationsCount}
displayIssueLocationsLink={this.props.displayIssueLocationsLink}
displayLocationMarkers={this.props.displayLocationMarkers}
highlightedLocationMessage={this.props.highlightedLocationMessage}
highlightedSymbols={this.props.highlightedSymbols}
issueLocations={this.props.issueLocations}
branchLike={branchLike}
displayIssueLocationsCount={displayIssueLocationsCount}
displayIssueLocationsLink={displayIssueLocationsLink}
displayLocationMarkers={displayLocationMarkers}
highlightedLocationMessage={highlightedLocationMessage}
highlightedSymbols={highlightedSymbols}
issueLocations={issueLocations}
issuePopup={issuePopup}
issues={this.props.issues}
issues={issues}
line={line}
onIssueChange={this.props.onIssueChange}
onIssuePopupToggle={this.props.onIssuePopupToggle}
@@ -181,9 +183,9 @@ export default class Line extends React.PureComponent<Props> {
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}
/>
</tr>
);

+ 8
- 4
server/sonar-web/src/main/js/components/SourceViewer/components/LineCoverage.tsx Dosyayı Görüntüle

@@ -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 (
<td className={className} data-line-number={line.line}>
<Tooltip overlay={getStatusTooltip(line)} placement="right">
<div className="source-line-bar" />
<Tooltip overlay={status} placement="right">
<div aria-label={status} className="source-line-bar" />
</Tooltip>
</td>
);
@@ -64,3 +66,5 @@ function getStatusTooltip(line: T.SourceLine) {
}
return undefined;
}

export default React.memo(LineCoverage);

+ 43
- 51
server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx Dosyayı Görüntüle

@@ -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<Props> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
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 ? (
<td className={className} data-index={index} data-line-number={line.line}>
<Toggler
onRequestClose={this.closePopup}
open={popupOpen}
overlay={this.props.renderDuplicationPopup(index, line.line)}>
<Tooltip
overlay={popupOpen ? undefined : translate('source_viewer.tooltip.duplicated_block')}
placement="right">
return duplicated ? (
<td className={className} data-index={index} data-line-number={line.line}>
<Tooltip
overlay={dropdownOpen ? undefined : translate('source_viewer.tooltip.duplicated_block')}
placement="right">
<div>
<Toggler
onRequestClose={() => setDropdownOpen(false)}
open={dropdownOpen}
overlay={
<DropdownOverlay placement={PopupPlacement.RightTop}>
{props.renderDuplicationPopup(index, line.line)}
</DropdownOverlay>
}>
<div
aria-label={translate('source_viewer.tooltip.duplicated_block')}
className="source-line-bar"
onClick={this.handleClick}
onClick={() => {
setDropdownOpen(true);
if (!blocksLoaded && line.duplicated && props.onClick) {
props.onClick(line);
}
}}
role="button"
tabIndex={0}
/>
</Tooltip>
</Toggler>
</td>
) : (
<td className={className} data-index={index} data-line-number={line.line}>
<div className="source-line-bar" />
</td>
);
}
</Toggler>
</div>
</Tooltip>
</td>
) : (
<td className={className} data-index={index} data-line-number={line.line}>
<div className="source-line-bar" />
</td>
);
}

export default React.memo(LineDuplicationBlock);

+ 0
- 60
server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplications.tsx Dosyayı Görüntüle

@@ -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<Props> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
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 = (
<td
className={className}
onClick={line.duplicated ? this.handleClick : undefined}
role={line.duplicated ? 'button' : undefined}
tabIndex={line.duplicated ? 0 : undefined}>
<div className="source-line-bar" />
</td>
);

return line.duplicated ? (
<Tooltip overlay={translate('source_viewer.tooltip.duplicated_line')} placement="right">
{cell}
</Tooltip>
) : (
cell
);
}
}

+ 31
- 25
server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx Dosyayı Görüntüle

@@ -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<Props> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
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<HTMLElement>) => {
e.preventDefault();
e.currentTarget.blur();
props.onClick();
};

return (
<td
className={className}
data-line-number={line.line}
onClick={hasIssues ? this.handleClick : undefined}
role={hasIssues ? 'button' : undefined}
tabIndex={hasIssues ? 0 : undefined}>
{mostImportantIssue != null && <IssueIcon type={mostImportantIssue.type} />}
{issues.length > 1 && <span className="source-line-issues-counter">{issues.length}</span>}
</td>
);
}
return (
<td className={className} data-line-number={line.line}>
{hasIssues && (
<span
aria-label={translate('source_viewer.issues_on_line', issuesOpen ? 'hide' : 'show')}
onClick={handleClick}
role="button"
tabIndex={0}>
{mostImportantIssue != null && <IssueIcon type={mostImportantIssue.type} />}
{issues.length > 1 && <span className="source-line-issues-counter">{issues.length}</span>}
</span>
)}
</td>
);
}

export default React.memo(LineIssuesIndicator);

+ 24
- 42
server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx Dosyayı Görüntüle

@@ -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<Props> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
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 ? (
<td
className="source-meta source-line-number"
data-line-number={lineNumber}
onClick={this.handleClick}
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
role="button"
tabIndex={0}>
<Toggler
onRequestClose={this.closePopup}
open={popupOpen}
overlay={<LineOptionsPopup line={line} />}
/>
</td>
) : (
<td className="source-meta source-line-number" />
);
}
export function LineNumber({ line }: LineNumberProps) {
const { line: lineNumber } = line;
const hasLineNumber = !!lineNumber;
return hasLineNumber ? (
<td className="source-meta source-line-number" data-line-number={lineNumber}>
<Dropdown
overlay={<LineOptionsPopup line={line} />}
overlayPlacement={PopupPlacement.RightTop}>
<span
aria-label={translateWithParameters('source_viewer.line_X', lineNumber)}
role="button">
{lineNumber}
</span>
</Dropdown>
</td>
) : (
<td className="source-meta source-line-number" />
);
}

export default React.memo(LineNumber);

+ 13
- 18
server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx Dosyayı Görüntüle

@@ -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 (
<SourceViewerContext.Consumer>
{({ branchLike, file }) => (
<DropdownOverlay placement={PopupPlacement.RightTop}>
<div className="source-viewer-bubble-popup nowrap">
<Link
className="js-get-permalink"
onClick={event => {
event.stopPropagation();
}}
rel="noopener noreferrer"
target="_blank"
to={getCodeUrl(file.project, branchLike, file.key, line.line)}>
{translate('component_viewer.get_permalink')}
</Link>
</div>
</DropdownOverlay>
<div className="source-viewer-bubble-popup nowrap">
<Link
className="js-get-permalink"
rel="noopener noreferrer"
target="_blank"
to={getCodeUrl(file.project, branchLike, file.key, line.line)}>
{translate('component_viewer.get_permalink')}
</Link>
</div>
)}
</SourceViewerContext.Consumer>
);
}

export default React.memo(LineOptionsPopup);

+ 31
- 39
server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx Dosyayı Görüntüle

@@ -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<Props> {
handleClick = (event: React.MouseEvent<HTMLElement>) => {
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 = (
<div className="source-line-scm-inner">
{isSCMChanged(line, previousLine) ? line.scmAuthor || '…' : ' '}
</div>
);

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) && (
<div className="source-line-scm-inner" data-author={line.scmAuthor || '…'} />
);
return hasPopup ? (
<td
className="source-meta source-line-scm"
data-line-number={line.line}
onClick={this.handleClick}
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-to-interactive-role
role="button"
tabIndex={0}>
<Toggler
onRequestClose={this.closePopup}
open={popupOpen}
overlay={<SCMPopup line={line} />}>
{cell}
</Toggler>
return (
<td className="source-meta source-line-scm" data-line-number={line.line}>
<Dropdown overlay={<SCMPopup line={line} />} overlayPlacement={PopupPlacement.RightTop}>
<div aria-label={ariaLabel} role="button">
{cell}
</div>
</Dropdown>
</td>
) : (
);
} else {
return (
<td className="source-meta source-line-scm" data-line-number={line.line}>
{cell}
</td>
@@ -80,3 +70,5 @@ function isSCMChanged(s: T.SourceLine, p: T.SourceLine | undefined) {
}
return changed;
}

export default React.memo(LineSCM);

+ 27
- 16
server/sonar-web/src/main/js/components/SourceViewer/components/SCMPopup.tsx Dosyayı Görüntüle

@@ -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 (
<DropdownOverlay placement={PopupPlacement.RightTop}>
<div className="source-viewer-bubble-popup abs-width-400">
{hasAuthor && <div>{line.scmAuthor}</div>}
{line.scmDate && (
<div className={classNames({ 'spacer-top': hasAuthor })}>
<DateFormatter date={line.scmDate} />
</div>
)}
{line.scmRevision && <div className="spacer-top">{line.scmRevision}</div>}
</div>
</DropdownOverlay>
<div className="source-viewer-bubble-popup abs-width-400">
{hasAuthor && (
<div>
<h4>{translate('author')}</h4>
{line.scmAuthor}
</div>
)}
{hasDate && (
<div className={classNames({ 'spacer-top': hasAuthor })}>
<h4>{translate('source_viewer.tooltip.scm.commited_on')}</h4>
<DateFormatter date={line.scmDate!} />
</div>
)}
{line.scmRevision && (
<div className={classNames({ 'spacer-top': hasAuthor || hasDate })}>
<h4>{translate('source_viewer.tooltip.scm.revision')}</h4>
{line.scmRevision}
</div>
)}
</div>
);
}

export default React.memo(SCMPopup);

+ 0
- 2
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx Dosyayı Görüntüle

@@ -106,9 +106,7 @@ function shallowRender(props: Partial<Line['props']> = {}) {
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()}

+ 18
- 16
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCoverage-test.tsx Dosyayı Görüntüle

@@ -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(<LineCoverage line={line} />);
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(<LineCoverage line={line} />);
expect(wrapper).toMatchSnapshot();
});

it('render line with unknown coverage', () => {
const line: T.SourceLine = { line: 3 };
const wrapper = shallow(<LineCoverage line={line} />);
expect(wrapper).toMatchSnapshot();
});
function shallowRender(props: Partial<LineCoverageProps> = {}) {
return shallow(<LineCoverage line={{ line: 3, coverageStatus: 'covered' }} {...props} />);
}

+ 38
- 26
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplicationBlock-test.tsx Dosyayı Görüntüle

@@ -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(
<LineDuplicationBlock
duplicated={true}
index={1}
line={line}
onPopupToggle={onPopupToggle}
popupOpen={false}
renderDuplicationPopup={jest.fn()}
/>
);
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<LineDuplicationBlockProps> = {}) {
return shallow<LineDuplicationBlockProps>(
<LineDuplicationBlock
duplicated={false}
blocksLoaded={false}
duplicated={true}
index={1}
line={line}
onPopupToggle={jest.fn()}
popupOpen={false}
line={{ line: 3, duplicated: true }}
renderDuplicationPopup={jest.fn()}
{...props}
/>
);
expect(wrapper).toMatchSnapshot();
});
}

+ 0
- 39
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineDuplications-test.tsx Dosyayı Görüntüle

@@ -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(<LineDuplications line={line} onClick={onClick} />);
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(<LineDuplications line={line} onClick={onClick} />);
expect(wrapper).toMatchSnapshot();
});

+ 16
- 15
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx Dosyayı Görüntüle

@@ -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<LineIssuesIndicator['props']> = {}) {
function shallowRender(props: Partial<LineIssuesIndicatorProps> = {}) {
return shallow(
<LineIssuesIndicator
issues={[

+ 7
- 12
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineNumber-test.tsx Dosyayı Görüntüle

@@ -19,18 +19,13 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { click } from 'sonar-ui-common/helpers/testUtils';
import LineNumber from '../LineNumber';
import { LineNumber, LineNumberProps } from '../LineNumber';

it('render line 3', () => {
const line = { line: 3 };
const wrapper = shallow(<LineNumber line={line} onPopupToggle={jest.fn()} popupOpen={false} />);
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(<LineNumber line={line} onPopupToggle={jest.fn()} popupOpen={false} />);
expect(wrapper).toMatchSnapshot();
});
function shallowRender(props: Partial<LineNumberProps> = {}) {
return shallow(<LineNumber line={{ line: 3 }} {...props} />);
}

+ 2
- 2
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineOptionsPopup-test.tsx Dosyayı Görüntüle

@@ -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(<LineOptionsPopup line={line} />).dive();
expect(wrapper).toMatchSnapshot();

+ 17
- 42
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineSCM-test.tsx Dosyayı Görüntüle

@@ -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(
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={previousLine} />
);
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(
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={undefined} />
);
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(
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={previousLine} />
);
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(
<LineSCM line={line} onPopupToggle={jest.fn()} popupOpen={false} previousLine={previousLine} />
function shallowRender(props: Partial<LineSCMProps> = {}) {
return shallow(
<LineSCM
line={{ line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' }}
previousLine={{ line: 2, scmAuthor: 'bar', scmDate: '2017-01-02' }}
{...props}
/>
);
expect(wrapper).toMatchSnapshot();
});

it('should open popup', () => {
const line = { line: 3, scmAuthor: 'foo', scmDate: '2017-01-01' };
const onPopupToggle = jest.fn();
const wrapper = shallow(
<LineSCM line={line} onPopupToggle={onPopupToggle} popupOpen={false} previousLine={undefined} />
);
click(wrapper.find('[role="button"]'));
expect(onPopupToggle).toBeCalledWith({ line: 3, name: 'scm' });
});
}

+ 22
- 4
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/SCMPopup-test.tsx Dosyayı Görüntüle

@@ -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(<SCMPopup line={line} />)).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<SCMPopupProps> = {}) {
return shallow(
<SCMPopup
line={{ line: 3, scmAuthor: 'foo', scmDate: '2017-01-01', scmRevision: 'bar' }}
{...props}
/>
);
}

+ 21
- 60
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/Line-test.tsx.snap Dosyayı Görüntüle

@@ -5,7 +5,7 @@ exports[`should render correctly 1`] = `
className="source-line source-line-filtered"
data-line-number={16}
>
<LineNumber
<Memo(LineNumber)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -19,10 +19,8 @@ exports[`should render correctly 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<LineSCM
<Memo(LineSCM)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -36,8 +34,6 @@ exports[`should render correctly 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<td
className="source-meta source-line-issues"
@@ -151,7 +147,7 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = `
className="source-line source-line-highlighted source-line-filtered source-line-last"
data-line-number={16}
>
<LineNumber
<Memo(LineNumber)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -165,10 +161,8 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<LineSCM
<Memo(LineSCM)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -182,8 +176,6 @@ exports[`should render correctly for last, new, and highlighted lines 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<td
className="source-meta source-line-issues"
@@ -297,7 +289,7 @@ exports[`should render correctly with coverage 1`] = `
className="source-line source-line-filtered"
data-line-number={16}
>
<LineNumber
<Memo(LineNumber)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -311,10 +303,8 @@ exports[`should render correctly with coverage 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<LineSCM
<Memo(LineSCM)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -328,13 +318,11 @@ exports[`should render correctly with coverage 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<td
className="source-meta source-line-issues"
/>
<LineCoverage
<Memo(LineCoverage)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -458,7 +446,7 @@ exports[`should render correctly with duplication information 1`] = `
className="source-line source-line-filtered"
data-line-number={16}
>
<LineNumber
<Memo(LineNumber)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -472,10 +460,8 @@ exports[`should render correctly with duplication information 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<LineSCM
<Memo(LineSCM)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -489,29 +475,12 @@ exports[`should render correctly with duplication information 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<td
className="source-meta source-line-issues"
/>
<LineDuplications
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
"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]}
/>
<LineDuplicationBlock
<Memo(LineDuplicationBlock)
blocksLoaded={true}
duplicated={false}
index={0}
key="0"
@@ -528,11 +497,11 @@ exports[`should render correctly with duplication information 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
onClick={[MockFunction]}
renderDuplicationPopup={[MockFunction]}
/>
<LineDuplicationBlock
<Memo(LineDuplicationBlock)
blocksLoaded={true}
duplicated={false}
index={1}
key="1"
@@ -549,11 +518,10 @@ exports[`should render correctly with duplication information 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
renderDuplicationPopup={[MockFunction]}
/>
<LineDuplicationBlock
<Memo(LineDuplicationBlock)
blocksLoaded={true}
duplicated={false}
index={2}
key="2"
@@ -570,8 +538,6 @@ exports[`should render correctly with duplication information 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
renderDuplicationPopup={[MockFunction]}
/>
<LineCode
@@ -683,7 +649,7 @@ exports[`should render correctly with issues info 1`] = `
className="source-line source-line-filtered"
data-line-number={16}
>
<LineNumber
<Memo(LineNumber)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -697,10 +663,8 @@ exports[`should render correctly with issues info 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<LineSCM
<Memo(LineSCM)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -714,10 +678,8 @@ exports[`should render correctly with issues info 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<LineIssuesIndicator
<Memo(LineIssuesIndicator)
issues={
Array [
Object {
@@ -784,6 +746,7 @@ exports[`should render correctly with issues info 1`] = `
},
]
}
issuesOpen={false}
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -908,7 +871,7 @@ exports[`should render correctly: no SCM 1`] = `
className="source-line source-line-filtered"
data-line-number={16}
>
<LineNumber
<Memo(LineNumber)
line={
Object {
"code": "<span class=\\"k\\">import</span> java.util.<span class=\\"sym-9 sym\\">ArrayList</span>;",
@@ -922,8 +885,6 @@ exports[`should render correctly: no SCM 1`] = `
"scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0",
}
}
onPopupToggle={[MockFunction]}
popupOpen={false}
/>
<td
className="source-meta source-line-issues"

+ 39
- 3
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCoverage-test.tsx.snap Dosyayı Görüntüle

@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render covered line 1`] = `
exports[`should render correctly: covered 1`] = `
<td
className="source-meta source-line-coverage source-line-covered"
data-line-number={3}
@@ -10,13 +10,14 @@ exports[`render covered line 1`] = `
placement="right"
>
<div
aria-label="source_viewer.tooltip.covered"
className="source-line-bar"
/>
</Tooltip>
</td>
`;

exports[`render line with unknown coverage 1`] = `
exports[`should render correctly: no data 1`] = `
<td
className="source-meta source-line-coverage"
data-line-number={3}
@@ -31,7 +32,41 @@ exports[`render line with unknown coverage 1`] = `
</td>
`;

exports[`render uncovered line 1`] = `
exports[`should render correctly: partially covered, 0 conditions 1`] = `
<td
className="source-meta source-line-coverage source-line-partially-covered"
data-line-number={3}
>
<Tooltip
overlay="source_viewer.tooltip.partially-covered"
placement="right"
>
<div
aria-label="source_viewer.tooltip.partially-covered"
className="source-line-bar"
/>
</Tooltip>
</td>
`;

exports[`should render correctly: partially covered, 10 conditions 1`] = `
<td
className="source-meta source-line-coverage source-line-partially-covered"
data-line-number={3}
>
<Tooltip
overlay="source_viewer.tooltip.partially-covered"
placement="right"
>
<div
aria-label="source_viewer.tooltip.partially-covered"
className="source-line-bar"
/>
</Tooltip>
</td>
`;

exports[`should render correctly: uncovered 1`] = `
<td
className="source-meta source-line-coverage source-line-uncovered"
data-line-number={3}
@@ -41,6 +76,7 @@ exports[`render uncovered line 1`] = `
placement="right"
>
<div
aria-label="source_viewer.tooltip.uncovered"
className="source-line-bar"
/>
</Tooltip>

+ 27
- 19
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplicationBlock-test.tsx.snap Dosyayı Görüntüle

@@ -1,33 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render duplicated line 1`] = `
exports[`should render correctly: default 1`] = `
<td
className="source-meta source-line-duplications-extra source-line-duplicated"
className="source-meta source-line-duplications source-line-duplicated"
data-index={1}
data-line-number={3}
>
<Toggler
onRequestClose={[Function]}
open={false}
<Tooltip
overlay="source_viewer.tooltip.duplicated_block"
placement="right"
>
<Tooltip
overlay="source_viewer.tooltip.duplicated_block"
placement="right"
>
<div
className="source-line-bar"
onClick={[Function]}
role="button"
tabIndex={0}
/>
</Tooltip>
</Toggler>
<div>
<Toggler
onRequestClose={[Function]}
open={false}
overlay={
<DropdownOverlay
placement="right-top"
/>
}
>
<div
aria-label="source_viewer.tooltip.duplicated_block"
className="source-line-bar"
onClick={[Function]}
role="button"
tabIndex={0}
/>
</Toggler>
</div>
</Tooltip>
</td>
`;

exports[`render not duplicated line 1`] = `
exports[`should render correctly: not duplicated 1`] = `
<td
className="source-meta source-line-duplications-extra"
className="source-meta source-line-duplications"
data-index={1}
data-line-number={3}
>

+ 0
- 29
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineDuplications-test.tsx.snap Dosyayı Görüntüle

@@ -1,29 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render duplicated line 1`] = `
<Tooltip
overlay="source_viewer.tooltip.duplicated_line"
placement="right"
>
<td
className="source-meta source-line-duplications source-line-duplicated"
onClick={[Function]}
role="button"
tabIndex={0}
>
<div
className="source-line-bar"
/>
</td>
</Tooltip>
`;

exports[`render not duplicated line 1`] = `
<td
className="source-meta source-line-duplications"
>
<div
className="source-line-bar"
/>
</td>
`;

+ 27
- 19
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap Dosyayı Görüntüle

@@ -1,44 +1,52 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
exports[`should render correctly: default 1`] = `
<td
className="source-meta source-line-issues source-line-with-issues"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<IssueIcon
type="BUG"
/>
<span
className="source-line-issues-counter"
aria-label="source_viewer.issues_on_line.show"
onClick={[Function]}
role="button"
tabIndex={0}
>
2
<IssueIcon
type="BUG"
/>
<span
className="source-line-issues-counter"
>
2
</span>
</span>
</td>
`;

exports[`should render correctly 2`] = `
exports[`should render correctly: diff issue types 1`] = `
<td
className="source-meta source-line-issues source-line-with-issues"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<IssueIcon
type="VULNERABILITY"
/>
<span
className="source-line-issues-counter"
aria-label="source_viewer.issues_on_line.show"
onClick={[Function]}
role="button"
tabIndex={0}
>
2
<IssueIcon
type="VULNERABILITY"
/>
<span
className="source-line-issues-counter"
>
2
</span>
</span>
</td>
`;

exports[`should render correctly for no issues 1`] = `
exports[`should render correctly: no issues 1`] = `
<td
className="source-meta source-line-issues"
data-line-number={3}

+ 18
- 15
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineNumber-test.tsx.snap Dosyayı Görüntüle

@@ -1,24 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`render line 0 1`] = `
<td
className="source-meta source-line-number"
/>
`;

exports[`render line 3 1`] = `
exports[`should render correctly: default 1`] = `
<td
className="source-meta source-line-number"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<Toggler
onRequestClose={[Function]}
open={false}
<Dropdown
overlay={
<LineOptionsPopup
<Memo(LineOptionsPopup)
line={
Object {
"line": 3,
@@ -26,6 +15,20 @@ exports[`render line 3 1`] = `
}
/>
}
/>
overlayPlacement="right-top"
>
<span
aria-label="source_viewer.line_X.3"
role="button"
>
3
</span>
</Dropdown>
</td>
`;

exports[`should render correctly: no line number 1`] = `
<td
className="source-meta source-line-number"
/>
`;

+ 23
- 28
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineOptionsPopup-test.tsx.snap Dosyayı Görüntüle

@@ -1,33 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render 1`] = `
<DropdownOverlay
placement="right-top"
exports[`should render correctly 1`] = `
<div
className="source-viewer-bubble-popup nowrap"
>
<div
className="source-viewer-bubble-popup nowrap"
>
<Link
className="js-get-permalink"
onClick={[Function]}
onlyActiveOnIndex={false}
rel="noopener noreferrer"
style={Object {}}
target="_blank"
to={
Object {
"pathname": "/code",
"query": Object {
"branch": "feature",
"id": "prj",
"line": 3,
"selected": "foo",
},
}
<Link
className="js-get-permalink"
onlyActiveOnIndex={false}
rel="noopener noreferrer"
style={Object {}}
target="_blank"
to={
Object {
"pathname": "/code",
"query": Object {
"branch": "feature",
"id": "prj",
"line": 3,
"selected": "foo",
},
}
>
component_viewer.get_permalink
</Link>
</div>
</DropdownOverlay>
}
>
component_viewer.get_permalink
</Link>
</div>
`;

+ 43
- 65
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineSCM-test.tsx.snap Dosyayı Görüntüle

@@ -1,45 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`does not render scm details 1`] = `
exports[`should render correctly: default 1`] = `
<td
className="source-meta source-line-scm"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<Toggler
onRequestClose={[Function]}
open={false}
<Dropdown
overlay={
<SCMPopup
line={
Object {
"line": 3,
"scmAuthor": "foo",
"scmDate": "2017-01-01",
"scmRevision": "foo",
}
}
/>
}
/>
</td>
`;

exports[`render scm details 1`] = `
<td
className="source-meta source-line-scm"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<Toggler
onRequestClose={[Function]}
open={false}
overlay={
<SCMPopup
<Memo(SCMPopup)
line={
Object {
"line": 3,
@@ -49,74 +17,84 @@ exports[`render scm details 1`] = `
}
/>
}
overlayPlacement="right-top"
>
<div
className="source-line-scm-inner"
data-author="foo"
/>
</Toggler>
aria-label="source_viewer.author_X.foo, source_viewer.click_for_scm_info"
role="button"
>
<div
className="source-line-scm-inner"
>
foo
</div>
</div>
</Dropdown>
</td>
`;

exports[`render scm details for the first line 1`] = `
exports[`should render correctly: no author 1`] = `
<td
className="source-meta source-line-scm"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<Toggler
onRequestClose={[Function]}
open={false}
<Dropdown
overlay={
<SCMPopup
<Memo(SCMPopup)
line={
Object {
"line": 3,
"scmAuthor": "foo",
"scmDate": "2017-01-01",
"scmRevision": "foo",
}
}
/>
}
overlayPlacement="right-top"
>
<div
className="source-line-scm-inner"
data-author="foo"
/>
</Toggler>
aria-label="source_viewer.click_for_scm_info"
role="button"
>
<div
className="source-line-scm-inner"
>
</div>
</div>
</Dropdown>
</td>
`;

exports[`renders ellipsis when no author info 1`] = `
exports[`should render correctly: same commit 1`] = `
<td
className="source-meta source-line-scm"
data-line-number={3}
onClick={[Function]}
role="button"
tabIndex={0}
>
<Toggler
onRequestClose={[Function]}
open={false}
<Dropdown
overlay={
<SCMPopup
<Memo(SCMPopup)
line={
Object {
"line": 3,
"scmAuthor": "foo",
"scmDate": "2017-01-01",
"scmRevision": "foo",
}
}
/>
}
overlayPlacement="right-top"
>
<div
className="source-line-scm-inner"
data-author="…"
/>
</Toggler>
aria-label="source_viewer.author_X.foo, source_viewer.click_for_scm_info"
role="button"
>
<div
className="source-line-scm-inner"
>
</div>
</div>
</Dropdown>
</td>
`;

+ 95
- 16
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/SCMPopup-test.tsx.snap Dosyayı Görüntüle

@@ -1,22 +1,101 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render 1`] = `
<DropdownOverlay
placement="right-top"
exports[`should render correctly: default 1`] = `
<div
className="source-viewer-bubble-popup abs-width-400"
>
<div>
<h4>
author
</h4>
foo
</div>
<div
className="spacer-top"
>
<h4>
source_viewer.tooltip.scm.commited_on
</h4>
<DateFormatter
date="2017-01-01"
/>
</div>
<div
className="spacer-top"
>
<h4>
source_viewer.tooltip.scm.revision
</h4>
bar
</div>
</div>
`;

exports[`should render correctly: no author 1`] = `
<div
className="source-viewer-bubble-popup abs-width-400"
>
<div
className=""
>
<h4>
source_viewer.tooltip.scm.commited_on
</h4>
<DateFormatter
date="2017-01-01"
/>
</div>
<div
className="spacer-top"
>
<h4>
source_viewer.tooltip.scm.revision
</h4>
bar
</div>
</div>
`;

exports[`should render correctly: no date 1`] = `
<div
className="source-viewer-bubble-popup abs-width-400"
>
<div>
<h4>
author
</h4>
foo
</div>
<div
className="spacer-top"
>
<h4>
source_viewer.tooltip.scm.revision
</h4>
bar
</div>
</div>
`;

exports[`should render correctly: no revision 1`] = `
<div
className="source-viewer-bubble-popup abs-width-400"
>
<div>
<h4>
author
</h4>
foo
</div>
<div
className="source-viewer-bubble-popup abs-width-400"
className="spacer-top"
>
<div>
foo
</div>
<div
className="spacer-top"
>
<DateFormatter
date="2017-01-01"
/>
</div>
</div>
</DropdownOverlay>
<h4>
source_viewer.tooltip.scm.commited_on
</h4>
<DateFormatter
date="2017-01-01"
/>
</div>
</div>
`;

+ 8
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Dosyayı Görüntüle

@@ -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...

Loading…
İptal
Kaydet