lineCoverageRed: '#a4030f',
purple: '#9139d4',
+ conciseIssueRed: '#d18582',
+
gray94: '#efefef',
gray80: '#cdcdcd',
gray71: '#b4b4b4',
gray40: '#404040',
transparentWhite: 'rgba(255,255,255,0.62)',
+ transparentGray: 'rgba(200, 200, 200, 0.5)',
disableGrayText: '#bbb',
disableGrayBorder: '#ddd',
);
}
- renderShortcutsForLocations() {
- const { openIssue } = this.state;
- if (!openIssue || (!openIssue.secondaryLocations.length && !openIssue.flows.length)) {
- return null;
- }
- const hasSeveralFlows = openIssue.flows.length > 1;
- return (
- <div className="pull-right note">
- <span className="shortcut-button little-spacer-right">alt</span>
- <span className="little-spacer-right">{'+'}</span>
- <span className="shortcut-button little-spacer-right">↑</span>
- <span className="shortcut-button little-spacer-right">↓</span>
- {hasSeveralFlows && (
- <span>
- <span className="shortcut-button little-spacer-right">←</span>
- <span className="shortcut-button little-spacer-right">→</span>
- </span>
- )}
- {translate('issues.to_navigate_issue_locations')}
- </div>
- );
- }
-
renderPage() {
const { checkAll, loading, openIssue, paging } = this.state;
return (
selectedIndex={selectedIndex}
/>
)}
- {this.renderShortcutsForLocations()}
</div>
</div>
</div>
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export interface Props {
+ issue: Pick<T.Issue, 'flows' | 'secondaryLocations'> | undefined;
+}
+
+export default function LocationNavigationKeyboardShortcuts({ issue }: Props) {
+ if (!issue || (!issue.secondaryLocations.length && !issue.flows.length)) {
+ return null;
+ }
+ const hasSeveralFlows = issue.flows.length > 1;
+ return (
+ <div className="navigation-keyboard-shortcuts big-spacer-top text-center">
+ <span>
+ alt + ↑ ↓ {hasSeveralFlows && <>←→</>}
+ {translate('issues.to_navigate_issue_locations')}
+ </span>
+ </div>
+ );
+}
mockLoggedInUser,
mockRouter,
mockIssue,
- mockLocation
+ mockLocation,
+ mockEvent
} from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
+import {
+ enableLocationsNavigator,
+ selectNextLocation,
+ selectPreviousLocation,
+ selectNextFlow,
+ selectPreviousFlow
+} from '../../actions';
const ISSUES = [
{ key: 'foo' } as T.Issue,
expect(result.paging.pageIndex).toBe(3);
});
+describe('keydown event handler', () => {
+ const wrapper = shallowRender();
+ const instance = wrapper.instance();
+ jest.spyOn(instance, 'setState');
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
+ it('should handle alt', () => {
+ instance.handleKeyDown(mockEvent({ keyCode: 18 }));
+ expect(instance.setState).toHaveBeenCalledWith(enableLocationsNavigator);
+ });
+ it('should handle alt+↓', () => {
+ instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 40 }));
+ expect(instance.setState).toHaveBeenCalledWith(selectNextLocation);
+ });
+ it('should handle alt+↑', () => {
+ instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 38 }));
+ expect(instance.setState).toHaveBeenCalledWith(selectPreviousLocation);
+ });
+ it('should handle alt+←', () => {
+ instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 37 }));
+ expect(instance.setState).toHaveBeenCalledWith(selectPreviousFlow);
+ });
+ it('should handle alt+→', () => {
+ instance.handleKeyDown(mockEvent({ altKey: true, keyCode: 39 }));
+ expect(instance.setState).toHaveBeenCalledWith(selectNextFlow);
+ });
+});
+
function fetchIssuesMockFactory(keyCount = 0, lineCount = 1) {
return jest.fn().mockImplementation(({ p }: any) =>
Promise.resolve({
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 React from 'react';
+import { shallow } from 'enzyme';
+import LocationNavigationKeyboardShortcuts, { Props } from '../LocationNavigationKeyboardShortcuts';
+import { mockFlowLocation } from '../../../../helpers/testMocks';
+
+it('should render correctly', () => {
+ expect(shallowRender().type()).toBeNull();
+ expect(shallowRender({ issue: { flows: [], secondaryLocations: [] } }).type()).toBeNull();
+ expect(
+ shallowRender({ issue: { flows: [], secondaryLocations: [mockFlowLocation()] } })
+ ).toMatchSnapshot();
+ expect(
+ shallowRender({ issue: { flows: [[mockFlowLocation()]], secondaryLocations: [] } })
+ ).toMatchSnapshot();
+});
+
+const shallowRender = (props: Partial<Props> = {}) => {
+ return shallow(<LocationNavigationKeyboardShortcuts issue={undefined} {...props} />);
+};
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="navigation-keyboard-shortcuts big-spacer-top text-center"
+>
+ <span>
+ alt + ↑ ↓
+ issues.to_navigate_issue_locations
+ </span>
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div
+ className="navigation-keyboard-shortcuts big-spacer-top text-center"
+>
+ <span>
+ alt + ↑ ↓
+ issues.to_navigate_issue_locations
+ </span>
+</div>
+`;
import * as classNames from 'classnames';
import ConciseIssueLocations from './ConciseIssueLocations';
import ConciseIssueLocationsNavigator from './ConciseIssueLocationsNavigator';
-import SeverityHelper from '../../../components/shared/SeverityHelper';
+import LocationNavigationKeyboardShortcuts from '../components/LocationNavigationKeyboardShortcuts';
import TypeHelper from '../../../components/shared/TypeHelper';
interface Props {
}
}
+ handleClick = (event: React.MouseEvent<HTMLElement>) => {
+ event.preventDefault();
+ this.props.onClick(this.props.issue.key);
+ };
+
handleScroll = () => {
const { selectedFlowIndex } = this.props;
const { flows, secondaryLocations } = this.props.issue;
- const locations =
- selectedFlowIndex !== undefined
- ? flows[selectedFlowIndex]
- : flows.length > 0
- ? flows[0]
- : secondaryLocations;
+ const locations = flows.length > 0 ? flows[selectedFlowIndex || 0] : secondaryLocations;
if (!locations || locations.length < 15) {
// if there are no locations, or there are just few
}
};
- handleClick = (event: React.MouseEvent<HTMLElement>) => {
- event.preventDefault();
- this.props.onClick(this.props.issue.key);
- };
-
render() {
const { issue, selected } = this.props;
{issue.message}
</div>
<div className="concise-issue-box-attributes">
- <TypeHelper type={issue.type} />
- <SeverityHelper className="big-spacer-left" severity={issue.severity} />
+ <TypeHelper className="display-block little-spacer-right" type={issue.type} />
<ConciseIssueLocations
issue={issue}
onFlowSelect={this.props.onFlowSelect}
/>
</div>
{selected && (
- <ConciseIssueLocationsNavigator
- issue={issue}
- onLocationSelect={this.props.onLocationSelect}
- scroll={this.props.scroll}
- selectedFlowIndex={this.props.selectedFlowIndex}
- selectedLocationIndex={this.props.selectedLocationIndex}
- />
+ <>
+ <ConciseIssueLocationsNavigator
+ issue={issue}
+ onLocationSelect={this.props.onLocationSelect}
+ scroll={this.props.scroll}
+ selectedFlowIndex={this.props.selectedFlowIndex}
+ selectedLocationIndex={this.props.selectedLocationIndex}
+ />
+ <LocationNavigationKeyboardShortcuts issue={issue} />
+ </>
)}
</div>
);
*/
import * as React from 'react';
import ConciseIssueLocationBadge from './ConciseIssueLocationBadge';
+import { Button } from '../../../components/ui/buttons';
interface Props {
issue: Pick<T.Issue, 'flows' | 'secondaryLocations'>;
collapsed: boolean;
}
-const LIMIT = 3;
+const LIMIT = 8;
export default class ConciseIssueLocations extends React.PureComponent<Props, State> {
state: State = { collapsed: true };
- handleExpandClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
- event.preventDefault();
+ handleExpandClick = () => {
this.setState({ collapsed: false });
};
renderExpandButton() {
return (
- <a className="little-spacer-left link-no-underline" href="#" onClick={this.handleExpandClick}>
+ <Button
+ className="concise-issue-expand location-index link-no-underline"
+ onClick={this.handleExpandClick}>
...
- </a>
+ </Button>
);
}
);
});
- return this.state.collapsed ? (
- <div className="concise-issue-locations pull-right">
- {badges.slice(0, LIMIT)}
- {badges.length > LIMIT && this.renderExpandButton()}
- </div>
- ) : (
- <div className="concise-issue-locations spacer-top">{badges}</div>
+ if (!this.state.collapsed || badges.length <= LIMIT) {
+ return badges;
+ }
+
+ return (
+ <>
+ {badges.slice(0, LIMIT - 1)}
+ {this.renderExpandButton()}
+ </>
);
}
}
import { getLocations } from '../utils';
interface Props {
- issue: Pick<T.Issue, 'component' | 'key' | 'flows' | 'secondaryLocations'>;
+ issue: Pick<T.Issue, 'component' | 'key' | 'flows' | 'secondaryLocations' | 'type'>;
onLocationSelect: (index: number) => void;
scroll: (element: Element) => void;
selectedFlowIndex: number | undefined;
{locations.map((location, index) => (
<ConciseIssueLocationsNavigatorLocation
index={index}
+ issueType={this.props.issue.type}
key={index}
message={location.msg}
onClick={this.props.onLocationSelect}
scroll={this.props.scroll}
selected={index === this.props.selectedLocationIndex}
+ totalCount={locations.length}
/>
))}
</div>
interface Props {
index: number;
+ issueType: T.IssueType;
message: string | undefined;
onClick: (index: number) => void;
scroll: (element: Element) => void;
selected: boolean;
+ totalCount: number;
}
export default class ConciseIssueLocationsNavigatorLocation extends React.PureComponent<Props> {
this.props.onClick(this.props.index);
};
+ prefixMessage(index: number, message = '', totalCount: number) {
+ switch (index) {
+ case 0:
+ return 'source: ' + message;
+ case totalCount - 1:
+ return 'sink: ' + message;
+ default:
+ return message;
+ }
+ }
+
render() {
+ const { index, issueType, message, selected, totalCount } = this.props;
+
return (
<div className="little-spacer-top" ref={node => (this.node = node)}>
<a
className="concise-issue-locations-navigator-location"
href="#"
onClick={this.handleClick}>
- <LocationIndex selected={this.props.selected}>{this.props.index + 1}</LocationIndex>
- <LocationMessage selected={this.props.selected}>{this.props.message}</LocationMessage>
+ <LocationIndex selected={selected}>{index + 1}</LocationIndex>
+ <LocationMessage selected={selected}>
+ {issueType === 'VULNERABILITY'
+ ? this.prefixMessage(index, message, totalCount)
+ : message}
+ </LocationMessage>
</a>
</div>
);
import { collapsePath } from '../../../helpers/path';
interface Props {
- issue: Pick<T.Issue, 'key'>;
+ issue: Pick<T.Issue, 'key' | 'type'>;
locations: T.FlowLocation[];
onLocationSelect: (index: number) => void;
scroll: (element: Element) => void;
return (
<ConciseIssueLocationsNavigatorLocation
index={index}
+ issueType={this.props.issue.type}
key={index}
message={message}
onClick={this.props.onLocationSelect}
scroll={this.props.scroll}
selected={index === this.props.selectedLocationIndex}
+ totalCount={this.props.locations.length}
/>
);
};
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 React from 'react';
+import { shallow } from 'enzyme';
+import ConciseIssueBox from '../ConciseIssueBox';
+import { mockIssue } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+
+it('should render correctly', async () => {
+ const wrapper = shallowRender();
+ await waitAndUpdate(wrapper);
+ expect(wrapper).toMatchSnapshot();
+
+ expect(shallowRender({ issue: mockIssue(true) })).toMatchSnapshot();
+});
+
+const shallowRender = (props: Partial<ConciseIssueBox['props']> = {}) => {
+ return shallow(
+ <ConciseIssueBox
+ issue={mockIssue()}
+ onClick={jest.fn()}
+ onFlowSelect={jest.fn()}
+ onLocationSelect={jest.fn()}
+ scroll={jest.fn()}
+ selected={true}
+ selectedFlowIndex={0}
+ selectedLocationIndex={0}
+ {...props}
+ />
+ );
+};
import * as React from 'react';
import { shallow } from 'enzyme';
import ConciseIssueLocationsNavigator from '../ConciseIssueLocationsNavigator';
+import { mockIssue } from '../../../../helpers/testMocks';
const location1: T.FlowLocation = {
component: 'foo',
};
it('should render secondary locations in the same file', () => {
- const issue = {
+ const issue = mockIssue(false, {
component: 'foo',
key: '',
flows: [],
secondaryLocations: [location1, location2]
- };
+ });
expect(
shallow(
<ConciseIssueLocationsNavigator
});
it('should render flow locations in the same file', () => {
- const issue = {
+ const issue = mockIssue(false, {
component: 'foo',
key: '',
flows: [[location1, location2]],
secondaryLocations: []
- };
+ });
expect(
shallow(
<ConciseIssueLocationsNavigator
});
it('should render selected flow locations in the same file', () => {
- const issue = {
+ const issue = mockIssue(false, {
component: 'foo',
key: '',
flows: [[location1, location2]],
secondaryLocations: [location1]
- };
+ });
expect(
shallow(
<ConciseIssueLocationsNavigator
});
it('should render flow locations in different file', () => {
- const issue = {
+ const issue = mockIssue(false, {
component: 'foo',
key: '',
flows: [[location1, location3]],
secondaryLocations: []
- };
+ });
expect(
shallow(
<ConciseIssueLocationsNavigator
});
it('should not render locations', () => {
- const issue = { component: 'foo', key: '', flows: [], secondaryLocations: [] };
+ const issue = mockIssue(false, {
+ component: 'foo',
+ key: '',
+ flows: [],
+ secondaryLocations: []
+ });
const wrapper = shallow(
<ConciseIssueLocationsNavigator
issue={issue}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 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 React from 'react';
+import { shallow } from 'enzyme';
+import ConciseIssueLocationsNavigatorLocation from '../ConciseIssueLocationsNavigatorLocation';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+it('should render vulnerabilities correctly', () => {
+ expect(shallowRender({ index: 0, issueType: 'VULNERABILITY', totalCount: 4 })).toMatchSnapshot();
+ expect(shallowRender({ index: 1, issueType: 'VULNERABILITY', totalCount: 4 })).toMatchSnapshot();
+ expect(shallowRender({ index: 3, issueType: 'VULNERABILITY', totalCount: 4 })).toMatchSnapshot();
+});
+
+const shallowRender = (props: Partial<ConciseIssueLocationsNavigatorLocation['props']> = {}) => {
+ return shallow(
+ <ConciseIssueLocationsNavigatorLocation
+ index={0}
+ issueType={'BUG'}
+ message={''}
+ onClick={jest.fn()}
+ scroll={jest.fn()}
+ selected={true}
+ totalCount={5}
+ {...props}
+ />
+ );
+};
it('should render', () => {
const wrapper = shallow(
<CrossFileLocationsNavigator
- issue={{ key: 'abcd' }}
+ issue={{ key: 'abcd', type: 'BUG' }}
locations={[location1, location2, location3]}
onLocationSelect={jest.fn()}
scroll={jest.fn()}
it('should render all locations', () => {
const wrapper = shallow(
<CrossFileLocationsNavigator
- issue={{ key: 'abcd' }}
+ issue={{ key: 'abcd', type: 'BUG' }}
locations={[location1, location2]}
onLocationSelect={jest.fn()}
scroll={jest.fn()}
it('should expand all locations', () => {
const wrapper = shallow(
<CrossFileLocationsNavigator
- issue={{ key: 'abcd' }}
+ issue={{ key: 'abcd', type: 'BUG' }}
locations={[location1, location2, location3]}
onLocationSelect={jest.fn()}
scroll={jest.fn()}
it('should collapse locations when issue changes', () => {
const wrapper = shallow(
<CrossFileLocationsNavigator
- issue={{ key: 'abcd' }}
+ issue={{ key: 'abcd', type: 'BUG' }}
locations={[location1, location2, location3]}
onLocationSelect={jest.fn()}
scroll={jest.fn()}
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="concise-issue-box clearfix selected"
+>
+ <div
+ className="concise-issue-box-message"
+ onClick={[Function]}
+ role="listitem"
+ tabIndex={0}
+ >
+ Reduce the number of conditional operators (4) used in the expression
+ </div>
+ <div
+ className="concise-issue-box-attributes"
+ >
+ <TypeHelper
+ className="display-block little-spacer-right"
+ type="BUG"
+ />
+ <ConciseIssueLocations
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ onFlowSelect={[MockFunction]}
+ selectedFlowIndex={0}
+ />
+ </div>
+ <ConciseIssueLocationsNavigator
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ onLocationSelect={[MockFunction]}
+ scroll={[MockFunction]}
+ selectedFlowIndex={0}
+ selectedLocationIndex={0}
+ />
+ <LocationNavigationKeyboardShortcuts
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ />
+</div>
+`;
+
+exports[`should render correctly 2`] = `
+<div
+ className="concise-issue-box clearfix selected"
+>
+ <div
+ className="concise-issue-box-message"
+ onClick={[Function]}
+ role="listitem"
+ tabIndex={0}
+ >
+ Reduce the number of conditional operators (4) used in the expression
+ </div>
+ <div
+ className="concise-issue-box-attributes"
+ >
+ <TypeHelper
+ className="display-block little-spacer-right"
+ type="BUG"
+ />
+ <ConciseIssueLocations
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [
+ Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ ],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ onFlowSelect={[MockFunction]}
+ selectedFlowIndex={0}
+ />
+ </div>
+ <ConciseIssueLocationsNavigator
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [
+ Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ ],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ onLocationSelect={[MockFunction]}
+ scroll={[MockFunction]}
+ selectedFlowIndex={0}
+ selectedLocationIndex={0}
+ />
+ <LocationNavigationKeyboardShortcuts
+ issue={
+ Object {
+ "actions": Array [],
+ "component": "main.js",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
+ "flows": Array [
+ Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ ],
+ "fromHotspot": false,
+ "key": "AVsae-CQS-9G3txfbFN2",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
+ "secondaryLocations": Array [
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ Object {
+ "component": "main.js",
+ "textRange": Object {
+ "endLine": 2,
+ "endOffset": 2,
+ "startLine": 1,
+ "startOffset": 1,
+ },
+ },
+ ],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
+ }
+ }
+ />
+</div>
+`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render one flow 1`] = `
-<div
- className="concise-issue-locations pull-right"
->
- <ConciseIssueLocationBadge
- count={3}
- key="0"
- onClick={[Function]}
- selected={false}
- />
-</div>
+<ConciseIssueLocationBadge
+ count={3}
+ key="0"
+ onClick={[Function]}
+ selected={false}
+/>
`;
exports[`should render secondary locations 1`] = `
-<div
- className="concise-issue-locations pull-right"
->
- <ConciseIssueLocationBadge
- count={3}
- key="-1"
- selected={true}
- />
-</div>
+<ConciseIssueLocationBadge
+ count={3}
+ key="-1"
+ selected={true}
+/>
`;
exports[`should render several flows 1`] = `
-<div
- className="concise-issue-locations pull-right"
->
+Array [
<ConciseIssueLocationBadge
count={3}
key="0"
onClick={[Function]}
selected={false}
- />
+ />,
<ConciseIssueLocationBadge
count={2}
key="1"
onClick={[Function]}
selected={false}
- />
+ />,
<ConciseIssueLocationBadge
count={3}
key="2"
onClick={[Function]}
selected={false}
- />
-</div>
+ />,
+]
`;
<CrossFileLocationsNavigator
issue={
Object {
+ "actions": Array [],
"component": "foo",
+ "componentLongName": "main.js",
+ "componentQualifier": "FIL",
+ "componentUuid": "foo1234",
+ "creationDate": "2017-03-01T09:36:01+0100",
"flows": Array [
Array [
Object {
},
],
],
+ "fromHotspot": false,
"key": "",
+ "line": 25,
+ "message": "Reduce the number of conditional operators (4) used in the expression",
+ "organization": "myorg",
+ "project": "myproject",
+ "projectKey": "foo",
+ "projectName": "Foo",
+ "projectOrganization": "org",
+ "rule": "javascript:S1067",
+ "ruleName": "foo",
"secondaryLocations": Array [],
+ "severity": "MAJOR",
+ "status": "OPEN",
+ "textRange": Object {
+ "endLine": 26,
+ "endOffset": 15,
+ "startLine": 25,
+ "startOffset": 0,
+ },
+ "transitions": Array [],
+ "type": "BUG",
}
}
locations={
>
<ConciseIssueLocationsNavigatorLocation
index={0}
+ issueType="BUG"
key="0"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={2}
/>
<ConciseIssueLocationsNavigatorLocation
index={1}
+ issueType="BUG"
key="1"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={2}
/>
</div>
`;
>
<ConciseIssueLocationsNavigatorLocation
index={0}
+ issueType="BUG"
key="0"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={2}
/>
<ConciseIssueLocationsNavigatorLocation
index={1}
+ issueType="BUG"
key="1"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={2}
/>
</div>
`;
>
<ConciseIssueLocationsNavigatorLocation
index={0}
+ issueType="BUG"
key="0"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={2}
/>
<ConciseIssueLocationsNavigatorLocation
index={1}
+ issueType="BUG"
key="1"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={2}
/>
</div>
`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correctly 1`] = `
+<div
+ className="little-spacer-top"
+>
+ <a
+ className="concise-issue-locations-navigator-location"
+ href="#"
+ onClick={[Function]}
+ >
+ <LocationIndex
+ selected={true}
+ >
+ 1
+ </LocationIndex>
+ <LocationMessage
+ selected={true}
+ />
+ </a>
+</div>
+`;
+
+exports[`should render vulnerabilities correctly 1`] = `
+<div
+ className="little-spacer-top"
+>
+ <a
+ className="concise-issue-locations-navigator-location"
+ href="#"
+ onClick={[Function]}
+ >
+ <LocationIndex
+ selected={true}
+ >
+ 1
+ </LocationIndex>
+ <LocationMessage
+ selected={true}
+ >
+ source:
+ </LocationMessage>
+ </a>
+</div>
+`;
+
+exports[`should render vulnerabilities correctly 2`] = `
+<div
+ className="little-spacer-top"
+>
+ <a
+ className="concise-issue-locations-navigator-location"
+ href="#"
+ onClick={[Function]}
+ >
+ <LocationIndex
+ selected={true}
+ >
+ 2
+ </LocationIndex>
+ <LocationMessage
+ selected={true}
+ />
+ </a>
+</div>
+`;
+
+exports[`should render vulnerabilities correctly 3`] = `
+<div
+ className="little-spacer-top"
+>
+ <a
+ className="concise-issue-locations-navigator-location"
+ href="#"
+ onClick={[Function]}
+ >
+ <LocationIndex
+ selected={true}
+ >
+ 4
+ </LocationIndex>
+ <LocationMessage
+ selected={true}
+ >
+ sink:
+ </LocationMessage>
+ </a>
+</div>
+`;
>
<ConciseIssueLocationsNavigatorLocation
index={0}
+ issueType="BUG"
key="0"
message="Do not use foo"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={3}
/>
</div>
</div>
>
<ConciseIssueLocationsNavigatorLocation
index={2}
+ issueType="BUG"
key="2"
message="Do not use bar"
onClick={[MockFunction]}
scroll={[MockFunction]}
selected={false}
+ totalCount={3}
/>
</div>
</div>
margin-top: var(--gridSize);
line-height: 16px;
font-size: var(--smallFontSize);
+ display: flex;
+ align-items: flex-start;
+ flex-wrap: wrap;
+ justify-content: flex-start;
}
.concise-issue-box:not(.selected) .location-index {
}
.concise-issue-locations {
- margin-right: -4px;
+ display: inline-block;
margin-bottom: -4px;
+ margin-left: var(--gridSize);
}
-.concise-issue-locations .location-index {
- margin-right: 4px;
+.concise-issue-box-attributes > .location-index {
margin-bottom: 4px;
+ margin-right: 4px;
+}
+
+.concise-issue-box-attributes > .concise-issue-expand {
+ background-color: transparent;
+ border: 1px solid var(--conciseIssueRed);
+ height: 16px;
+ color: var(--conciseIssueRed);
+ font-weight: bold;
+ font-size: 16px;
+ line-height: 8px;
+ padding-bottom: 6px;
+}
+
+.concise-issue-box-attributes > .concise-issue-expand:hover {
+ background-color: var(--conciseIssueRed);
+ color: white;
}
.concise-issue-locations-navigator-location {
top: 13px;
bottom: calc(-2 * var(--gridSize));
left: 4px;
- border-left: 1px dotted #d18582;
+ border-left: 1px dotted var(--conciseIssueRed);
content: '';
}
display: inline-block;
width: calc(1px + var(--gridSize));
height: calc(1px + var(--gridSize));
- border: 1px solid #d18582;
+ border: 1px solid var(--conciseIssueRed);
border-radius: 100%;
box-sizing: border-box;
background-color: var(--issueBgColor);
.bulk-change-radio-button:hover {
background-color: var(--barBackgroundColor);
}
+
+.navigation-keyboard-shortcuts > span {
+ background-color: var(--transparentGray);
+ border-radius: 16px;
+ display: inline-block;
+ font-size: var(--smallFontSize);
+ height: 16px;
+ padding: calc(var(--gridSize) / 2) var(--gridSize);
+}
type: 'BUG'
};
- function loc(): T.FlowLocation {
- return {
- component: 'main.js',
- textRange: { startLine: 1, startOffset: 1, endLine: 2, endOffset: 2 }
- };
- }
+ const loc = mockFlowLocation;
if (withLocations) {
issue.flows = [[loc(), loc(), loc()], [loc(), loc()]];
...overrides
};
}
+
+export function mockFlowLocation(overrides: Partial<T.FlowLocation> = {}): T.FlowLocation {
+ return {
+ component: 'main.js',
+ textRange: {
+ startLine: 1,
+ startOffset: 1,
+ endLine: 2,
+ endOffset: 2
+ },
+ ...overrides
+ };
+}