diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2019-07-01 18:53:17 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-07-18 20:21:12 +0200 |
commit | 437a3a305e14e6ac3413eaaf36f1cd68c5a79c28 (patch) | |
tree | f99d22461bf0f388e1fff7e809ae673ef1cd5607 /server/sonar-web | |
parent | 6ddad7c6256d38730615495b0bf8e189fba147cc (diff) | |
download | sonarqube-437a3a305e14e6ac3413eaaf36f1cd68c5a79c28.tar.gz sonarqube-437a3a305e14e6ac3413eaaf36f1cd68c5a79c28.zip |
SONAR-12022 Animate snippets
Diffstat (limited to 'server/sonar-web')
8 files changed, 1337 insertions, 1119 deletions
diff --git a/server/sonar-web/src/main/js/app/types.d.ts b/server/sonar-web/src/main/js/app/types.d.ts index 415a5f2e276..09accb78d73 100644 --- a/server/sonar-web/src/main/js/app/types.d.ts +++ b/server/sonar-web/src/main/js/app/types.d.ts @@ -792,6 +792,13 @@ declare namespace T { type: 'SHORT'; } + export interface Snippet { + start: number; + end: number; + index: number; + toDelete?: boolean; + } + export interface SnippetGroup extends SnippetsByComponent { locations: T.FlowLocation[]; } diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx index d9e14742a19..90332683273 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetViewer.tsx @@ -19,7 +19,13 @@ */ import * as React from 'react'; import * as classNames from 'classnames'; -import { createSnippets, expandSnippet, EXPAND_BY_LINES, MERGE_DISTANCE } from './utils'; +import { + createSnippets, + expandSnippet, + EXPAND_BY_LINES, + MERGE_DISTANCE, + linesForSnippets +} from './utils'; import SnippetViewer from './SnippetViewer'; import SourceViewerHeaderSlim from '../../../components/SourceViewer/SourceViewerHeaderSlim'; import getCoverageStatus from '../../../components/SourceViewer/helpers/getCoverageStatus'; @@ -57,11 +63,12 @@ interface State { highlightedSymbols: string[]; loading: boolean; openIssuesByLine: T.Dict<boolean>; - snippets: T.SourceLine[][]; + snippets: T.Snippet[]; } export default class ComponentSourceSnippetViewer extends React.PureComponent<Props, State> { mounted = false; + rootNodeRef = React.createRef<HTMLDivElement>(); state: State = { additionalLines: {}, highlightedSymbols: [], @@ -80,35 +87,78 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr } createSnippetsFromProps() { - const snippets = createSnippets( - this.props.snippetGroup.locations, - this.props.snippetGroup.sources, - this.props.last - ); + const snippets = createSnippets(this.props.snippetGroup.locations, this.props.last); this.setState({ snippets }); } + getNodes(index: number): { wrapper: HTMLElement; table: HTMLElement } | undefined { + const root = this.rootNodeRef.current; + if (!root) { + return undefined; + } + const element = root.querySelector(`#snippet-wrapper-${index}`); + if (!element) { + return undefined; + } + const wrapper = element.querySelector<HTMLElement>('.snippet'); + if (!wrapper) { + return undefined; + } + const table = wrapper.firstChild as HTMLElement; + if (!table) { + return undefined; + } + + return { wrapper, table }; + } + + setMaxHeight(index: number, value?: number, up = false) { + const nodes = this.getNodes(index); + + if (!nodes) { + return; + } + + const { wrapper, table } = nodes; + + const maxHeight = value !== undefined ? value : table.getBoundingClientRect().height; + + if (up) { + const startHeight = wrapper.getBoundingClientRect().height; + table.style.transition = 'none'; + table.style.marginTop = `${startHeight - maxHeight}px`; + + // animate! + setTimeout(() => { + table.style.transition = ''; + table.style.marginTop = '0px'; + wrapper.style.maxHeight = `${maxHeight + 20}px`; + }, 0); + } else { + wrapper.style.maxHeight = `${maxHeight + 20}px`; + } + } + expandBlock = (snippetIndex: number, direction: T.ExpandDirection) => { const { branchLike, snippetGroup } = this.props; const { key } = snippetGroup.component; const { snippets } = this.state; - - const snippet = snippets[snippetIndex]; - + const snippet = snippets.find(s => s.index === snippetIndex); + if (!snippet) { + return; + } // Extend by EXPAND_BY_LINES and add buffer for merging snippets const extension = EXPAND_BY_LINES + MERGE_DISTANCE - 1; - const range = direction === 'up' ? { - from: Math.max(1, snippet[0].line - extension), - to: snippet[0].line - 1 + from: Math.max(1, snippet.start - extension), + to: snippet.start - 1 } : { - from: snippet[snippet.length - 1].line + 1, - to: snippet[snippet.length - 1].line + extension + from: snippet.end + 1, + to: snippet.end + extension }; - getSources({ key, ...range, @@ -122,27 +172,55 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr }, {}) ) .then( - newLinesMapped => { - if (this.mounted) { - this.setState(({ additionalLines, snippets }) => { - const combinedLines = { ...additionalLines, ...newLinesMapped }; - - return { - additionalLines: combinedLines, - snippets: expandSnippet({ - direction, - lines: { ...combinedLines, ...this.props.snippetGroup.sources }, - snippetIndex, - snippets - }) - }; - }); - } - }, + newLinesMapped => this.animateBlockExpansion(snippetIndex, direction, newLinesMapped), () => {} ); }; + animateBlockExpansion( + snippetIndex: number, + direction: T.ExpandDirection, + newLinesMapped: T.Dict<T.SourceLine> + ) { + if (this.mounted) { + const { snippets } = this.state; + + const newSnippets = expandSnippet({ + direction, + snippetIndex, + snippets + }); + + const deletedSnippets = newSnippets.filter(s => s.toDelete); + + // set max-height to current height for CSS transitions + deletedSnippets.forEach(s => this.setMaxHeight(s.index)); + this.setMaxHeight(snippetIndex); + + this.setState( + ({ additionalLines, snippets }) => { + const combinedLines = { ...additionalLines, ...newLinesMapped }; + return { + additionalLines: combinedLines, + snippets + }; + }, + () => { + // Set max-height 0 to trigger CSS transitions + deletedSnippets.forEach(s => { + this.setMaxHeight(s.index, 0); + }); + this.setMaxHeight(snippetIndex, undefined, direction === 'up'); + + // Wait for transition to finish before updating dom + setTimeout(() => { + this.setState({ snippets: newSnippets.filter(s => !s.toDelete) }); + }, 200); + } + ); + } + } + expandComponent = () => { const { branchLike, snippetGroup } = this.props; const { key } = snippetGroup.component; @@ -152,7 +230,14 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr getSources({ key, ...getBranchLikeQuery(branchLike) }).then( lines => { if (this.mounted) { - this.setState({ loading: false, snippets: [lines] }); + this.setState(({ additionalLines }) => { + const combinedLines = { ...additionalLines, ...lines }; + return { + additionalLines: combinedLines, + loading: false, + snippets: [{ start: 0, end: lines[lines.length - 1].line, index: -1 }] + }; + }); } }, () => { @@ -222,7 +307,6 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr issue={this.props.issue} issuePopup={this.props.issuePopup} issuesByLine={issuesByLine} - key={index} last={last} loadDuplications={this.loadDuplications} locations={this.props.locations} @@ -240,19 +324,26 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr render() { const { branchLike, duplications, issue, issuesByLine, last, snippetGroup } = this.props; - const { loading, snippets } = this.state; + const { additionalLines, loading, snippets } = this.state; const locations = locationsByLine([issue]); const fullyShown = snippets.length === 1 && snippetGroup.component.measures && - snippets[0].length === parseInt(snippetGroup.component.measures.lines || '', 10); + snippets[0].end - snippets[0].start === + parseInt(snippetGroup.component.measures.lines || '', 10); + + const snippetLines = linesForSnippets(snippets, { + ...snippetGroup.sources, + ...additionalLines + }); return ( <div className={classNames('component-source-container', { 'source-duplications-expanded': duplications && duplications.length > 0 - })}> + })} + ref={this.rootNodeRef}> <SourceViewerHeaderSlim branchLike={branchLike} expandable={!fullyShown} @@ -260,15 +351,17 @@ export default class ComponentSourceSnippetViewer extends React.PureComponent<Pr onExpand={this.expandComponent} sourceViewerFile={snippetGroup.component} /> - {snippets.map((snippet, index) => - this.renderSnippet({ - snippet, - index, - issuesByLine: last ? issuesByLine : {}, - locationsByLine: last && index === snippets.length - 1 ? locations : {}, - last: last && index === snippets.length - 1 - }) - )} + {snippetLines.map((snippet, index) => ( + <div id={`snippet-wrapper-${snippets[index].index}`} key={snippets[index].index}> + {this.renderSnippet({ + snippet, + index: snippets[index].index, + issuesByLine: last ? issuesByLine : {}, + locationsByLine: last && index === snippets.length - 1 ? locations : {}, + last: last && index === snippets.length - 1 + })} + </div> + ))} </div> ); } diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx index daa6af037f4..91ded7f72fd 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/SnippetViewer.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import classNames from 'classnames'; import ExpandSnippetIcon from 'sonar-ui-common/components/icons/ExpandSnippetIcon'; import { scrollHorizontally } from 'sonar-ui-common/helpers/scrolling'; import { translate } from 'sonar-ui-common/helpers/l10n'; @@ -191,46 +192,48 @@ export default class SnippetViewer extends React.PureComponent<Props> { return ( <div className="source-viewer-code snippet" ref={this.node}> - <table className="source-table"> - <tbody> - {snippet[0].line > 1 && ( - <tr className="expand-block expand-block-above"> - <td colSpan={5}> - <button - aria-label={translate('source_viewer.expand_above')} - onClick={this.expandBlock('up')} - type="button"> - <ExpandSnippetIcon /> - </button> - </td> - </tr> - )} - {snippet.map((line, index) => - this.renderLine({ - displayDuplications, - index, - issuesForLine: issuesByLine[line.line] || [], - issueLocations: locationsByLine[line.line] || [], - line, - snippet, - symbols: symbols[line.line], - verticalBuffer: index === snippet.length - 1 ? verticalBuffer : 0 - }) - )} - {(!lastLine || snippet[snippet.length - 1].line < lastLine) && ( - <tr className="expand-block expand-block-below"> - <td colSpan={5}> - <button - aria-label={translate('source_viewer.expand_below')} - onClick={this.expandBlock('down')} - type="button"> - <ExpandSnippetIcon /> - </button> - </td> - </tr> - )} - </tbody> - </table> + <div> + {snippet[0].line > 1 && ( + <div className="expand-block expand-block-above"> + <button + aria-label={translate('source_viewer.expand_above')} + onClick={this.expandBlock('up')} + type="button"> + <ExpandSnippetIcon /> + </button> + </div> + )} + <table + className={classNames('source-table', { + 'expand-up': snippet[0].line > 1, + 'expand-down': !lastLine || snippet[snippet.length - 1].line < lastLine + })}> + <tbody> + {snippet.map((line, index) => + this.renderLine({ + displayDuplications, + index, + issuesForLine: issuesByLine[line.line] || [], + issueLocations: locationsByLine[line.line] || [], + line, + snippet, + symbols: symbols[line.line], + verticalBuffer: index === snippet.length - 1 ? verticalBuffer : 0 + }) + )} + </tbody> + </table> + {(!lastLine || snippet[snippet.length - 1].line < lastLine) && ( + <div className="expand-block expand-block-below"> + <button + aria-label={translate('source_viewer.expand_below')} + onClick={this.expandBlock('down')} + type="button"> + <ExpandSnippetIcon /> + </button> + </div> + )} + </div> </div> ); } diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetViewer-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetViewer-test.tsx index 2bfa852f381..0b78f105f23 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetViewer-test.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetViewer-test.tsx @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount, ReactWrapper } from 'enzyme'; import { times } from 'lodash'; import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; import ComponentSourceSnippetViewer from '../ComponentSourceSnippetViewer'; @@ -70,7 +70,7 @@ it('should expand block', async () => { expect(getSources).toHaveBeenCalledWith({ from: 19, key: 'a', to: 31 }); expect(wrapper.state('snippets')).toHaveLength(2); - expect(wrapper.state('snippets')[0]).toHaveLength(15); + expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 22, end: 36 }); expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(10); }); @@ -99,7 +99,7 @@ it('should expand full component', async () => { expect(getSources).toHaveBeenCalledWith({ key: 'a' }); expect(wrapper.state('snippets')).toHaveLength(1); - expect(wrapper.state('snippets')[0]).toHaveLength(14); + expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 }); }); it('should get the right branch when expanding', async () => { @@ -190,6 +190,107 @@ it('should correctly handle lines actions', () => { ); }); +describe('getNodes', () => { + const snippetGroup: T.SnippetGroup = { + component: mockSourceViewerFile(), + locations: [], + sources: [] + }; + const wrapper = mount<ComponentSourceSnippetViewer>( + <ComponentSourceSnippetViewer + branchLike={mockMainBranch()} + duplications={undefined} + duplicationsByLine={undefined} + highlightedLocationMessage={{ index: 0, text: '' }} + issue={mockIssue()} + issuesByLine={{}} + last={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()} + snippetGroup={snippetGroup} + /> + ); + + it('should return undefined if any node is missing', async () => { + await waitAndUpdate(wrapper); + const rootNode = wrapper.instance().rootNodeRef; + mockDom(rootNode.current!); + expect(wrapper.instance().getNodes(0)).toBeUndefined(); + expect(wrapper.instance().getNodes(1)).toBeUndefined(); + expect(wrapper.instance().getNodes(2)).toBeUndefined(); + }); + + it('should return elements if dom is correct', async () => { + await waitAndUpdate(wrapper); + const rootNode = wrapper.instance().rootNodeRef; + mockDom(rootNode.current!); + expect(wrapper.instance().getNodes(3)).not.toBeUndefined(); + }); +}); + +describe('getHeight', () => { + jest.useFakeTimers(); + + const snippetGroup: T.SnippetGroup = { + component: mockSourceViewerFile(), + locations: [], + sources: [] + }; + const wrapper = mount<ComponentSourceSnippetViewer>( + <ComponentSourceSnippetViewer + branchLike={mockMainBranch()} + duplications={undefined} + duplicationsByLine={undefined} + highlightedLocationMessage={{ index: 0, text: '' }} + issue={mockIssue()} + issuesByLine={{}} + last={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()} + snippetGroup={snippetGroup} + /> + ); + + it('should set maxHeight to current height', async () => { + await waitAndUpdate(wrapper); + + const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 }); + wrapper.instance().setMaxHeight(0); + + expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;'); + expect(nodes.table.getAttribute('style')).toBeNull(); + }); + + it('should set margin and then maxHeight for a nice upwards animation', async () => { + await waitAndUpdate(wrapper); + + const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 }); + wrapper.instance().setMaxHeight(0, undefined, true); + + expect(nodes.wrapper.getAttribute('style')).toBeNull(); + expect(nodes.table.getAttribute('style')).toBe('transition: none; margin-top: -26px;'); + + jest.runAllTimers(); + + expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;'); + expect(nodes.table.getAttribute('style')).toBe('margin-top: 0px;'); + }); +}); + function shallowRender(props: Partial<ComponentSourceSnippetViewer['props']> = {}) { const snippetGroup: T.SnippetGroup = { component: mockSourceViewerFile(), @@ -219,3 +320,47 @@ function shallowRender(props: Partial<ComponentSourceSnippetViewer['props']> = { /> ); } + +function mockDom(refNode: HTMLDivElement) { + refNode.querySelector = jest.fn(query => { + const index = query.split('-').pop(); + + switch (index) { + case '0': + return null; + case '1': + return mount(<div />).getDOMNode(); + case '2': + return mount( + <div> + <div className="snippet" /> + </div> + ).getDOMNode(); + case '3': + return mount( + <div> + <div className="snippet"> + <div /> + </div> + </div> + ).getDOMNode(); + default: + return null; + } + }); +} + +function mockDomForSizes( + componentWrapper: ReactWrapper<{}, {}, ComponentSourceSnippetViewer>, + { wrapperHeight = 0, tableHeight = 0 } +) { + const wrapper = mount(<div className="snippet" />).getDOMNode(); + wrapper.getBoundingClientRect = jest.fn().mockReturnValue({ height: wrapperHeight }); + const table = mount(<div />).getDOMNode(); + table.getBoundingClientRect = jest.fn().mockReturnValue({ height: tableHeight }); + componentWrapper.instance().getNodes = jest.fn().mockReturnValue({ + wrapper, + table + }); + return { wrapper, table }; +} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap index e2081bb97cb..1dccbb712e6 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/SnippetViewer-test.tsx.snap @@ -4,212 +4,206 @@ exports[`should render correctly 1`] = ` <div className="source-viewer-code snippet" > - <table - className="source-table" - > - <tbody> - <tr - className="expand-block expand-block-above" + <div> + <div + className="expand-block expand-block-above" + > + <button + aria-label="source_viewer.expand_above" + onClick={[Function]} + type="button" > - <td - colSpan={5} - > - <button - aria-label="source_viewer.expand_above" - onClick={[Function]} - type="button" - > - <ExpandSnippetIcon /> - </button> - </td> - </tr> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="5" - last={false} - 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": 5, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="6" - last={false} - 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": 6, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 5, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="7" - last={false} - 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": 7, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 6, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <tr - className="expand-block expand-block-below" + <ExpandSnippetIcon /> + </button> + </div> + <table + className="source-table expand-up expand-down" + > + <tbody> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="5" + last={false} + 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": 5, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="6" + last={false} + 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": 6, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 5, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="7" + last={false} + 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": 7, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 6, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + </tbody> + </table> + <div + className="expand-block expand-block-below" + > + <button + aria-label="source_viewer.expand_below" + onClick={[Function]} + type="button" > - <td - colSpan={5} - > - <button - aria-label="source_viewer.expand_below" - onClick={[Function]} - type="button" - > - <ExpandSnippetIcon /> - </button> - </td> - </tr> - </tbody> - </table> + <ExpandSnippetIcon /> + </button> + </div> + </div> </div> `; @@ -217,273 +211,267 @@ exports[`should render correctly when at the bottom of the file 1`] = ` <div className="source-viewer-code snippet" > - <table - className="source-table" - > - <tbody> - <tr - className="expand-block expand-block-above" + <div> + <div + className="expand-block expand-block-above" + > + <button + aria-label="source_viewer.expand_above" + onClick={[Function]} + type="button" > - <td - colSpan={5} - > - <button - aria-label="source_viewer.expand_above" - onClick={[Function]} - type="button" - > - <ExpandSnippetIcon /> - </button> - </td> - </tr> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="10" - last={false} - 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": 10, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="11" - last={false} - 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": 11, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 10, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="12" - last={false} - 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": 12, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 11, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="13" - last={false} - 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": 13, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 12, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <tr - className="expand-block expand-block-below" + <ExpandSnippetIcon /> + </button> + </div> + <table + className="source-table expand-up expand-down" + > + <tbody> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="10" + last={false} + 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": 10, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="11" + last={false} + 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": 11, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 10, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="12" + last={false} + 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": 12, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 11, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="13" + last={false} + 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": 13, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 12, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + </tbody> + </table> + <div + className="expand-block expand-block-below" + > + <button + aria-label="source_viewer.expand_below" + onClick={[Function]} + type="button" > - <td - colSpan={5} - > - <button - aria-label="source_viewer.expand_below" - onClick={[Function]} - type="button" - > - <ExpandSnippetIcon /> - </button> - </td> - </tr> - </tbody> - </table> + <ExpandSnippetIcon /> + </button> + </div> + </div> </div> `; @@ -491,440 +479,438 @@ exports[`should render correctly when at the top of the file 1`] = ` <div className="source-viewer-code snippet" > - <table - className="source-table" - > - <tbody> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="1" - last={false} - 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": 1, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="2" - last={false} - 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": 2, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 1, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="3" - last={false} - 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": 3, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 2, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="4" - last={false} - 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": 4, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 3, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="5" - last={false} - 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": 5, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 4, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="6" - last={false} - 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": 6, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 5, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <Line - branchLike={ - Object { - "analysisDate": "2018-01-01", - "isMain": true, - "name": "master", - } - } - displayAllIssues={false} - displayCoverage={true} - displayDuplications={false} - displayIssues={true} - displayLocationMarkers={true} - duplications={Array []} - duplicationsCount={0} - highlighted={false} - issueLocations={Array []} - issues={Array []} - key="7" - last={false} - 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": 7, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - loadDuplications={[MockFunction]} - onIssueChange={[MockFunction]} - onIssuePopupToggle={[MockFunction]} - onIssueSelect={[Function]} - onIssueUnselect={[Function]} - onIssuesClose={[MockFunction]} - onIssuesOpen={[MockFunction]} - onLinePopupToggle={[MockFunction]} - onLocationSelect={[MockFunction]} - onSymbolClick={[MockFunction]} - previousLine={ - 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": 6, - "scmAuthor": "simon.brandhof@sonarsource.com", - "scmDate": "2018-12-11T10:48:39+0100", - "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", - } - } - renderDuplicationPopup={[MockFunction]} - scroll={[Function]} - secondaryIssueLocations={Array []} - verticalBuffer={0} - /> - <tr - className="expand-block expand-block-below" + <div> + <table + className="source-table expand-down" + > + <tbody> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="1" + last={false} + 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": 1, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="2" + last={false} + 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": 2, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 1, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="3" + last={false} + 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": 3, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 2, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="4" + last={false} + 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": 4, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 3, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="5" + last={false} + 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": 5, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 4, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="6" + last={false} + 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": 6, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 5, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + <Line + branchLike={ + Object { + "analysisDate": "2018-01-01", + "isMain": true, + "name": "master", + } + } + displayAllIssues={false} + displayCoverage={true} + displayDuplications={false} + displayIssues={true} + displayLocationMarkers={true} + duplications={Array []} + duplicationsCount={0} + highlighted={false} + issueLocations={Array []} + issues={Array []} + key="7" + last={false} + 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": 7, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + loadDuplications={[MockFunction]} + onIssueChange={[MockFunction]} + onIssuePopupToggle={[MockFunction]} + onIssueSelect={[Function]} + onIssueUnselect={[Function]} + onIssuesClose={[MockFunction]} + onIssuesOpen={[MockFunction]} + onLinePopupToggle={[MockFunction]} + onLocationSelect={[MockFunction]} + onSymbolClick={[MockFunction]} + previousLine={ + 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": 6, + "scmAuthor": "simon.brandhof@sonarsource.com", + "scmDate": "2018-12-11T10:48:39+0100", + "scmRevision": "80f564becc0c0a1c9abaa006eca83a4fd278c3f0", + } + } + renderDuplicationPopup={[MockFunction]} + scroll={[Function]} + secondaryIssueLocations={Array []} + verticalBuffer={0} + /> + </tbody> + </table> + <div + className="expand-block expand-block-below" + > + <button + aria-label="source_viewer.expand_below" + onClick={[Function]} + type="button" > - <td - colSpan={5} - > - <button - aria-label="source_viewer.expand_below" - onClick={[Function]} - type="button" - > - <ExpandSnippetIcon /> - </button> - </td> - </tr> - </tbody> - </table> + <ExpandSnippetIcon /> + </button> + </div> + </div> </div> `; diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts index ad114d760a3..7f93c687f3a 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts @@ -17,13 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { keyBy, range } from 'lodash'; import { groupLocationsByComponent, createSnippets, expandSnippet } from '../utils'; -import { - mockFlowLocation, - mockSnippetsByComponent, - mockSourceLine -} from '../../../../helpers/testMocks'; +import { mockFlowLocation, mockSnippetsByComponent } from '../../../../helpers/testMocks'; describe('groupLocationsByComponent', () => { it('should handle empty args', () => { @@ -92,12 +87,11 @@ describe('createSnippets', () => { textRange: { startLine: 19, startOffset: 2, endLine: 19, endOffset: 3 } }) ], - mockSnippetsByComponent('', [14, 15, 16, 17, 18, 19, 20, 21, 22]).sources, false ); expect(results).toHaveLength(1); - expect(results[0]).toHaveLength(8); + expect(results[0]).toEqual({ index: 0, start: 14, end: 21 }); }); it('should merge snippets correctly, even when not in sequence', () => { @@ -113,13 +107,12 @@ describe('createSnippets', () => { textRange: { startLine: 14, startOffset: 2, endLine: 14, endOffset: 3 } }) ], - mockSnippetsByComponent('', [12, 13, 14, 15, 16, 17, 18, 45, 46, 47, 48, 49]).sources, false ); expect(results).toHaveLength(2); - expect(results[0]).toHaveLength(7); - expect(results[1]).toHaveLength(5); + expect(results[0]).toEqual({ index: 0, start: 12, end: 18 }); + expect(results[1]).toEqual({ index: 1, start: 45, end: 49 }); }); it('should merge three snippets together', () => { @@ -138,56 +131,46 @@ describe('createSnippets', () => { textRange: { startLine: 18, startOffset: 2, endLine: 18, endOffset: 3 } }) ], - mockSnippetsByComponent('', [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 45, 46, 47, 48, 49]) - .sources, false ); expect(results).toHaveLength(2); - expect(results[0]).toHaveLength(11); - expect(results[1]).toHaveLength(5); + expect(results[0]).toEqual({ index: 0, start: 14, end: 24 }); + expect(results[1]).toEqual({ index: 1, start: 45, end: 49 }); }); }); describe('expandSnippet', () => { it('should add lines above', () => { - const lines = keyBy(range(4, 19).map(line => mockSourceLine({ line })), 'line'); - const snippets = [[lines[14], lines[15], lines[16], lines[17], lines[18]]]; + const snippets = [{ start: 14, end: 18, index: 0 }]; - const result = expandSnippet({ direction: 'up', lines, snippetIndex: 0, snippets }); + const result = expandSnippet({ direction: 'up', snippetIndex: 0, snippets }); expect(result).toHaveLength(1); - expect(result[0]).toHaveLength(15); - expect(result[0].map(l => l.line)).toEqual(range(4, 19)); + expect(result[0]).toEqual({ start: 4, end: 18, index: 0 }); }); it('should add lines below', () => { - const lines = keyBy(range(4, 19).map(line => mockSourceLine({ line })), 'line'); - const snippets = [[lines[4], lines[5], lines[6], lines[7], lines[8]]]; + const snippets = [{ start: 4, end: 8, index: 0 }]; - const result = expandSnippet({ direction: 'down', lines, snippetIndex: 0, snippets }); + const result = expandSnippet({ direction: 'down', snippetIndex: 0, snippets }); expect(result).toHaveLength(1); - expect(result[0].map(l => l.line)).toEqual(range(4, 19)); + expect(result[0]).toEqual({ start: 4, end: 18, index: 0 }); }); it('should merge snippets if necessary', () => { - const lines = keyBy( - range(4, 23) - .concat(range(38, 43)) - .map(line => mockSourceLine({ line })), - 'line' - ); const snippets = [ - [lines[4], lines[5], lines[6], lines[7], lines[8]], - [lines[38], lines[39], lines[40], lines[41], lines[42]], - [lines[17], lines[18], lines[19], lines[20], lines[21]] + { index: 1, start: 4, end: 8 }, + { index: 2, start: 38, end: 42 }, + { index: 3, start: 17, end: 21 } ]; - const result = expandSnippet({ direction: 'down', lines, snippetIndex: 0, snippets }); + const result = expandSnippet({ direction: 'down', snippetIndex: 1, snippets }); - expect(result).toHaveLength(2); - expect(result[0].map(l => l.line)).toEqual(range(4, 22)); - expect(result[1].map(l => l.line)).toEqual(range(38, 43)); + expect(result).toHaveLength(3); + expect(result[0]).toEqual({ index: 1, start: 4, end: 21 }); + expect(result[1]).toEqual({ index: 2, start: 38, end: 42 }); + expect(result[2]).toEqual({ index: 3, start: 17, end: 21, toDelete: true }); }); }); diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts index 5c34f42dfbb..c900f543268 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/utils.ts @@ -42,62 +42,53 @@ function collision([startA, endA]: number[], [startB, endB]: number[]) { return !(startA > endB + MERGE_DISTANCE || endA < startB - MERGE_DISTANCE); } -export function createSnippets( - locations: T.FlowLocation[], - componentLines: T.LineMap = {}, - last: boolean -): T.SourceLine[][] { - return rangesToSnippets( - // For each location's range (2 above and 2 below), and then compare with other ranges - // to merge snippets that collide. - locations.reduce((snippets: Array<{ start: number; end: number }>, loc, index) => { - const startIndex = Math.max(1, loc.textRange.startLine - LINES_ABOVE); - const endIndex = - loc.textRange.endLine + - (last && index === locations.length - 1 ? LINES_BELOW_LAST : LINES_BELOW); - - let firstCollision: { start: number; end: number } | undefined; - - // Remove ranges that collide into the first collision - snippets = snippets.filter(snippet => { - if (collision([snippet.start, snippet.end], [startIndex, endIndex])) { - let keep = false; - // Check if we've already collided - if (!firstCollision) { - firstCollision = snippet; - keep = true; - } - // Merge with first collision: - firstCollision.start = Math.min(startIndex, snippet.start, firstCollision.start); - firstCollision.end = Math.max(endIndex, snippet.end, firstCollision.end); - - // remove the range if it was not the first collision - return keep; +export function createSnippets(locations: T.FlowLocation[], last: boolean): T.Snippet[] { + // For each location's range (2 above and 2 below), and then compare with other ranges + // to merge snippets that collide. + return locations.reduce((snippets: T.Snippet[], loc, index) => { + const startIndex = Math.max(1, loc.textRange.startLine - LINES_ABOVE); + const endIndex = + loc.textRange.endLine + + (last && index === locations.length - 1 ? LINES_BELOW_LAST : LINES_BELOW); + + let firstCollision: { start: number; end: number } | undefined; + + // Remove ranges that collide into the first collision + snippets = snippets.filter(snippet => { + if (collision([snippet.start, snippet.end], [startIndex, endIndex])) { + let keep = false; + // Check if we've already collided + if (!firstCollision) { + firstCollision = snippet; + keep = true; } - return true; - }); + // Merge with first collision: + firstCollision.start = Math.min(startIndex, snippet.start, firstCollision.start); + firstCollision.end = Math.max(endIndex, snippet.end, firstCollision.end); - if (firstCollision === undefined) { - snippets.push({ - start: startIndex, - end: endIndex - }); + // remove the range if it was not the first collision + return keep; } + return true; + }); + + if (firstCollision === undefined) { + snippets.push({ + start: startIndex, + end: endIndex, + index + }); + } - return snippets; - }, []), - componentLines - ); + return snippets; + }, []); } -function rangesToSnippets( - ranges: Array<{ start: number; end: number }>, - componentLines: T.LineMap -) { - return ranges - .map(range => { +export function linesForSnippets(snippets: T.Snippet[], componentLines: T.LineMap) { + return snippets + .map(snippet => { const lines = []; - for (let i = range.start; i <= range.end; i++) { + for (let i = snippet.start; i <= snippet.end; i++) { if (componentLines[i]) { lines.push(componentLines[i]); } @@ -133,51 +124,36 @@ export function groupLocationsByComponent( export function expandSnippet({ direction, - lines, snippetIndex, snippets }: { direction: T.ExpandDirection; - lines: T.LineMap; snippetIndex: number; - snippets: T.SourceLine[][]; + snippets: T.Snippet[]; }) { - const snippetToExpand = snippets[snippetIndex]; - - const snippetToExpandRange = { - start: Math.max(0, snippetToExpand[0].line - (direction === 'up' ? EXPAND_BY_LINES : 0)), - end: - snippetToExpand[snippetToExpand.length - 1].line + - (direction === 'down' ? EXPAND_BY_LINES : 0) - }; + const snippetToExpand = snippets.find(s => s.index === snippetIndex); + if (!snippetToExpand) { + throw new Error(`Snippet ${snippetIndex} not found`); + } + + snippetToExpand.start = Math.max( + 0, + snippetToExpand.start - (direction === 'up' ? EXPAND_BY_LINES : 0) + ); + snippetToExpand.end += direction === 'down' ? EXPAND_BY_LINES : 0; - const ranges: Array<{ start: number; end: number }> = []; - - snippets.forEach((snippet, index: number) => { - const snippetRange = { - start: snippet[0].line, - end: snippet[snippet.length - 1].line - }; - - if (index === snippetIndex) { - // keep expanded snippet - ranges.push(snippetToExpandRange); - } else if ( - collision( - [snippetRange.start, snippetRange.end], - [snippetToExpandRange.start, snippetToExpandRange.end] - ) - ) { + return snippets.map(snippet => { + if (snippet.index === snippetIndex) { + return snippetToExpand; + } + if (collision([snippet.start, snippet.end], [snippetToExpand.start, snippetToExpand.end])) { // Merge with expanded snippet - snippetToExpandRange.start = Math.min(snippetRange.start, snippetToExpandRange.start); - snippetToExpandRange.end = Math.max(snippetRange.end, snippetToExpandRange.end); - } else { - // No collision, jsut keep the snippet - ranges.push(snippetRange); + snippetToExpand.start = Math.min(snippet.start, snippetToExpand.start); + snippetToExpand.end = Math.max(snippet.end, snippetToExpand.end); + snippet.toDelete = true; } + return snippet; }); - - return rangesToSnippets(ranges, lines); } export function inSnippet(line: number, snippet: T.SourceLine[]) { diff --git a/server/sonar-web/src/main/js/apps/issues/styles.css b/server/sonar-web/src/main/js/apps/issues/styles.css index e89e0916a7a..700eddf3eec 100644 --- a/server/sonar-web/src/main/js/apps/issues/styles.css +++ b/server/sonar-web/src/main/js/apps/issues/styles.css @@ -242,13 +242,28 @@ margin: var(--gridSize); border: 1px solid var(--gray80); overflow-x: auto; + overflow-y: hidden; + transition: max-height 0.2s; } -.snippet > table { +.snippet > div { + display: table; + width: 100%; + position: relative; + transition: margin-top 0.2s; +} + +.snippet table { + width: 100%; +} + +.expand-block { + position: absolute; + z-index: 2; width: 100%; } -.expand-block > td > button { +.expand-block > button { background: transparent; box-sizing: border-box; color: var(--secondFontColor); @@ -259,17 +274,27 @@ text-align: left; cursor: pointer; } -.expand-block > td > button:hover, -.expand-block > td > button:focus, -.expand-block > td > button:active { +.expand-block > button:hover, +.expand-block > button:focus, +.expand-block > button:active { color: var(--darkBlue); outline: none; } .expand-block-above { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAAXNSR0IArs4c6QAAADdJREFUCB1dzMEKADAIAlBd1v9/bcc2YgRjHh8qq2qTxCQzsX4wM6y30RARF3sy0Es1SIK7Y64OpCES1W69JS4AAAAASUVORK5CYII='); + top: 0; } .expand-block-below { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wQQBjQEQVd5jwAAADhJREFUCNddyTEKADEMA8GVA/7/Z+PGwUp1cGTaYe/tv5lxrLWoKj6SiMzkjZDEG7JtANt0N+ccLrB/KZxXTt7fAAAAAElFTkSuQmCC'); + bottom: 0; +} + +.source-table.expand-up { + margin-top: 20px; +} + +.source-table.expand-down { + margin-bottom: 20px; } .issues-my-issues-filter { |