]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11897 Increase sidebar issue box usability
authorJeremy Davis <jeremy.davis@sonarsource.com>
Wed, 3 Apr 2019 12:07:18 +0000 (14:07 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 6 May 2019 09:01:14 +0000 (11:01 +0200)
22 files changed:
server/sonar-web/src/main/js/app/theme.js
server/sonar-web/src/main/js/apps/issues/components/App.tsx
server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocations.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigator.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueLocationsNavigatorLocation.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/CrossFileLocationsNavigator.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigator-test.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/CrossFileLocationsNavigator-test.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocations-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigator-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/CrossFileLocationsNavigator-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/styles.css
server/sonar-web/src/main/js/helpers/testMocks.ts

index 62053ce4d4d8f8988d2426a0a313dd5d543f6693..b4371f19e0a8428e55067db136e822a65fb037b6 100644 (file)
@@ -37,6 +37,8 @@ module.exports = {
   lineCoverageRed: '#a4030f',
   purple: '#9139d4',
 
+  conciseIssueRed: '#d18582',
+
   gray94: '#efefef',
   gray80: '#cdcdcd',
   gray71: '#b4b4b4',
@@ -45,6 +47,7 @@ module.exports = {
   gray40: '#404040',
 
   transparentWhite: 'rgba(255,255,255,0.62)',
+  transparentGray: 'rgba(200, 200, 200, 0.5)',
 
   disableGrayText: '#bbb',
   disableGrayBorder: '#ddd',
index 159bff63ae6cc05f915c7f0f406d9bd342d1c60d..0b7307f2ce50f024602789581cd0904dd8034f7e 100644 (file)
@@ -1036,29 +1036,6 @@ export class App extends React.PureComponent<Props, State> {
     );
   }
 
-  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 (
@@ -1134,7 +1111,6 @@ export class App extends React.PureComponent<Props, State> {
                     selectedIndex={selectedIndex}
                   />
                 )}
-                {this.renderShortcutsForLocations()}
               </div>
             </div>
           </div>
diff --git a/server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx b/server/sonar-web/src/main/js/apps/issues/components/LocationNavigationKeyboardShortcuts.tsx
new file mode 100644 (file)
index 0000000..069ff62
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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>
+  );
+}
index da45c568f93375476d0fbab6fdfc7f99883c7ef9..2b473a7f88e5947f01222779ba104b972b17c85d 100644 (file)
@@ -24,9 +24,17 @@ import {
   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,
@@ -181,6 +189,37 @@ it('should fetch issues until defined', async () => {
   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({
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/LocationNavigationKeyboardShortcuts-test.tsx
new file mode 100644 (file)
index 0000000..75d3768
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * 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} />);
+};
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/LocationNavigationKeyboardShortcuts-test.tsx.snap
new file mode 100644 (file)
index 0000000..e992592
--- /dev/null
@@ -0,0 +1,23 @@
+// 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>
+`;
index be24582def82401f31412644d2f2821931384910..a2f931167847ffd0125a44fab064a058c2a9e8d2 100644 (file)
@@ -21,7 +21,7 @@ import * as React from 'react';
 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 {
@@ -51,16 +51,16 @@ export default class ConciseIssueBox extends React.PureComponent<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
@@ -72,11 +72,6 @@ export default class ConciseIssueBox extends React.PureComponent<Props> {
     }
   };
 
-  handleClick = (event: React.MouseEvent<HTMLElement>) => {
-    event.preventDefault();
-    this.props.onClick(this.props.issue.key);
-  };
-
   render() {
     const { issue, selected } = this.props;
 
@@ -100,8 +95,7 @@ export default class ConciseIssueBox extends React.PureComponent<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}
@@ -109,13 +103,16 @@ export default class ConciseIssueBox extends React.PureComponent<Props> {
           />
         </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>
     );
index cb9675a30cc5fe6365d6ac3ffe891e917c3d7b30..ff99a7558f7550814517014e52b80498e5c04b66 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import ConciseIssueLocationBadge from './ConciseIssueLocationBadge';
+import { Button } from '../../../components/ui/buttons';
 
 interface Props {
   issue: Pick<T.Issue, 'flows' | 'secondaryLocations'>;
@@ -30,21 +31,22 @@ interface State {
   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>
     );
   }
 
@@ -74,13 +76,15 @@ export default class ConciseIssueLocations extends React.PureComponent<Props, St
       );
     });
 
-    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()}
+      </>
     );
   }
 }
index eb1b76f835668a60bb6e3454a8c1b433754b4281..0def805f2fa68553a4f0d8b6ad643a3f631ed127 100644 (file)
@@ -24,7 +24,7 @@ import CrossFileLocationsNavigator from './CrossFileLocationsNavigator';
 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;
@@ -61,11 +61,13 @@ export default class ConciseIssueLocationsNavigator extends React.PureComponent<
           {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>
index ce2cb55cedda3f0d05d51f0aa33acc914ea58f67..af3539ecc151ae4ed27e2ab62464049bed5bebc4 100644 (file)
@@ -23,10 +23,12 @@ import LocationMessage from '../../../components/common/LocationMessage';
 
 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> {
@@ -49,15 +51,32 @@ export default class ConciseIssueLocationsNavigatorLocation extends React.PureCo
     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>
     );
index 912a94f1aa46b1795143a397e8baf843cdd455d6..268c9f3b51a1c26d05be62726516774252a7adf0 100644 (file)
@@ -23,7 +23,7 @@ import { translateWithParameters } from '../../../helpers/l10n';
 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;
@@ -111,11 +111,13 @@ export default class CrossFileLocationsNavigator extends React.PureComponent<Pro
     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}
       />
     );
   };
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueBox-test.tsx
new file mode 100644 (file)
index 0000000..b7f18bf
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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}
+    />
+  );
+};
index 0e037e9e9125b0e37d12cc1da2be0fbc3cd1573b..28da9ead72bbdff87aa1d443f1ff4893dd6114f0 100644 (file)
@@ -20,6 +20,7 @@
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import ConciseIssueLocationsNavigator from '../ConciseIssueLocationsNavigator';
+import { mockIssue } from '../../../../helpers/testMocks';
 
 const location1: T.FlowLocation = {
   component: 'foo',
@@ -43,12 +44,12 @@ const location3: T.FlowLocation = {
 };
 
 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
@@ -63,12 +64,12 @@ it('should render secondary locations in the same file', () => {
 });
 
 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
@@ -83,12 +84,12 @@ it('should render flow locations in the same file', () => {
 });
 
 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
@@ -103,12 +104,12 @@ it('should render selected flow locations in the same file', () => {
 });
 
 it('should render flow locations in different file', () => {
-  const issue = {
+  const issue = mockIssue(false, {
     component: 'foo',
     key: '',
     flows: [[location1, location3]],
     secondaryLocations: []
-  };
+  });
   expect(
     shallow(
       <ConciseIssueLocationsNavigator
@@ -123,7 +124,12 @@ it('should render flow locations in different file', () => {
 });
 
 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}
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssueLocationsNavigatorLocation-test.tsx
new file mode 100644 (file)
index 0000000..e65843e
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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}
+    />
+  );
+};
index bf4e4f9a698a49a28522c0f6451c76b55f59964d..2705d36ae5f54d41e6cc20ff696b7869cf259bef 100644 (file)
@@ -46,7 +46,7 @@ const location3: T.FlowLocation = {
 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()}
@@ -63,7 +63,7 @@ it('should render', () => {
 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()}
@@ -76,7 +76,7 @@ it('should render all locations', () => {
 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()}
@@ -92,7 +92,7 @@ it('should expand all locations', () => {
 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()}
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueBox-test.tsx.snap
new file mode 100644 (file)
index 0000000..4acba10
--- /dev/null
@@ -0,0 +1,476 @@
+// 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>
+`;
index f9973b06e0c456a75c56aeebfc76aaf09fba5004..cd78e9dfdaf32d4bbdcd54c5c15007e6e19923e7 100644 (file)
@@ -1,51 +1,41 @@
 // 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>
+  />,
+]
 `;
index f2512176f96120d5c6836aa4c91fb3a39deebdb2..6a9dc041d256097101d3337811745b6d5b39f086 100644 (file)
@@ -4,7 +4,12 @@ exports[`should render flow locations in different file 1`] = `
 <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 {
@@ -31,8 +36,28 @@ exports[`should render flow locations in different file 1`] = `
           },
         ],
       ],
+      "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={
@@ -72,19 +97,23 @@ exports[`should render flow locations in the same file 1`] = `
 >
   <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>
 `;
@@ -95,19 +124,23 @@ exports[`should render secondary locations in the same file 1`] = `
 >
   <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>
 `;
@@ -118,19 +151,23 @@ exports[`should render selected flow locations in the same file 1`] = `
 >
   <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>
 `;
diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/__snapshots__/ConciseIssueLocationsNavigatorLocation-test.tsx.snap
new file mode 100644 (file)
index 0000000..9ca4229
--- /dev/null
@@ -0,0 +1,89 @@
+// 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>
+`;
index e1b734ee3e58749eded7677bece122d16ce50ffe..86cb38f4c5fd7a4ae653928fd8f5407c3c1d7bce 100644 (file)
@@ -21,11 +21,13 @@ exports[`should render 1`] = `
     >
       <ConciseIssueLocationsNavigatorLocation
         index={0}
+        issueType="BUG"
         key="0"
         message="Do not use foo"
         onClick={[MockFunction]}
         scroll={[MockFunction]}
         selected={false}
+        totalCount={3}
       />
     </div>
   </div>
@@ -64,11 +66,13 @@ exports[`should render 1`] = `
     >
       <ConciseIssueLocationsNavigatorLocation
         index={2}
+        issueType="BUG"
         key="2"
         message="Do not use bar"
         onClick={[MockFunction]}
         scroll={[MockFunction]}
         selected={false}
+        totalCount={3}
       />
     </div>
   </div>
index a6687a9e5b0c8f90f5faf179082705d58d1821e2..1f2dba6d6a5cb309c225e6b8a8020eaebe81231c 100644 (file)
   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);
+}
index dd21ec1fec25826b799d0b292b600c87122ab1b9..cc30abeb85e4853feda8e11642568cc4da98cdbd 100644 (file)
@@ -211,12 +211,7 @@ export function mockIssue(withLocations = false, overrides: Partial<T.Issue> = {
     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()]];
@@ -517,3 +512,16 @@ export function mockLanguage(overrides: Partial<T.Language> = {}): T.Language {
     ...overrides
   };
 }
+
+export function mockFlowLocation(overrides: Partial<T.FlowLocation> = {}): T.FlowLocation {
+  return {
+    component: 'main.js',
+    textRange: {
+      startLine: 1,
+      startOffset: 1,
+      endLine: 2,
+      endOffset: 2
+    },
+    ...overrides
+  };
+}