]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16303 Add frontend IT for advance rule
authorMathieu Suen <mathieu.suen@sonarsource.com>
Tue, 24 May 2022 12:08:29 +0000 (14:08 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 24 May 2022 20:10:14 +0000 (20:10 +0000)
28 files changed:
server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewerWrapper-test.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerBase-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerCode-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeaderSlim-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssueList-test.tsx
server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/issueLocations-test.ts
server/sonar-web/src/main/js/helpers/mocks/sources.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/testMocks.ts
server/sonar-web/src/main/js/types/issues.ts
server/sonar-web/src/main/js/types/types.ts

index 3d1ed2a6a2246baa7cf29b5002962af154976333..e961edf5b669a4953ea9199f6a3003375d4c497c 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { cloneDeep } from 'lodash';
+import { cloneDeep, keyBy, range, times } from 'lodash';
+import {
+  mockSnippetsByComponent,
+  mockSourceLine,
+  mockSourceViewerFile
+} from '../../helpers/mocks/sources';
 import { RequestData } from '../../helpers/request';
 import { getStandards } from '../../helpers/security-standard';
-import { mockPaging } from '../../helpers/testMocks';
-import { RawFacet, RawIssuesResponse, ReferencedComponent } from '../../types/issues';
+import { mockPaging, mockRawIssue, mockRuleDetails } from '../../helpers/testMocks';
+import { BranchParameters } from '../../types/branch-like';
+import { RawFacet, RawIssue, RawIssuesResponse, ReferencedComponent } from '../../types/issues';
 import { Standards } from '../../types/security';
-import { searchIssues } from '../issues';
+import {
+  Dict,
+  RuleActivation,
+  RuleDescriptionSections,
+  RuleDetails,
+  SnippetsByComponent,
+  SourceViewerFile
+} from '../../types/types';
+import { getComponentForSourceViewer, getSources } from '../components';
+import { getIssueFlowSnippets, searchIssues } from '../issues';
+import { getRuleDetails } from '../rules';
 
 function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
   return {
@@ -34,16 +50,123 @@ function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
   };
 }
 
+interface IssueData {
+  issue: RawIssue;
+  snippets: Dict<SnippetsByComponent>;
+}
+
 export default class IssuesServiceMock {
   isAdmin = false;
   standards?: Standards;
+  sourceViewerFiles: SourceViewerFile[];
+  list: IssueData[];
 
   constructor() {
-    (searchIssues as jest.Mock).mockImplementation(this.listHandler);
-  }
-
-  reset() {
-    this.setIsAdmin(false);
+    this.sourceViewerFiles = [
+      mockSourceViewerFile('file.foo', 'project'),
+      mockSourceViewerFile('file.bar', 'project')
+    ];
+    this.list = [
+      {
+        issue: mockRawIssue(false, {
+          key: 'issue0',
+          component: 'project:file.foo',
+          message: 'Issue on file',
+          rule: 'simpleRuleId',
+          textRange: undefined,
+          line: undefined
+        }),
+        snippets: {}
+      },
+      {
+        issue: mockRawIssue(false, {
+          key: 'issue1',
+          component: 'project:file.foo',
+          message: 'Fix this',
+          rule: 'simpleRuleId',
+          textRange: {
+            startLine: 10,
+            endLine: 10,
+            startOffset: 0,
+            endOffset: 2
+          },
+          flows: [
+            {
+              locations: [
+                {
+                  component: 'project:file.foo',
+                  textRange: {
+                    startLine: 1,
+                    endLine: 1,
+                    startOffset: 0,
+                    endOffset: 1
+                  }
+                }
+              ]
+            },
+            {
+              locations: [
+                {
+                  component: 'project:file.bar',
+                  textRange: {
+                    startLine: 20,
+                    endLine: 20,
+                    startOffset: 0,
+                    endOffset: 1
+                  }
+                }
+              ]
+            }
+          ]
+        }),
+        snippets: keyBy(
+          [
+            mockSnippetsByComponent(
+              'file.foo',
+              'project',
+              times(40, i => i + 1)
+            ),
+            mockSnippetsByComponent(
+              'file.bar',
+              'project',
+              times(40, i => i + 1)
+            )
+          ],
+          'component.key'
+        )
+      },
+      {
+        issue: mockRawIssue(false, {
+          key: 'issue2',
+          component: 'project:file.bar',
+          message: 'Fix that',
+          rule: 'advancedRuleId',
+          textRange: {
+            startLine: 25,
+            endLine: 25,
+            startOffset: 0,
+            endOffset: 1
+          }
+        }),
+        snippets: keyBy(
+          [
+            mockSnippetsByComponent(
+              'file.bar',
+              'project',
+              times(40, i => i + 20)
+            )
+          ],
+          'component.key'
+        )
+      }
+    ];
+    (searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues);
+    (getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails);
+    (getIssueFlowSnippets as jest.Mock).mockImplementation(this.handleGetIssueFlowSnippets);
+    (getSources as jest.Mock).mockImplementation(this.handleGetSources);
+    (getComponentForSourceViewer as jest.Mock).mockImplementation(
+      this.handleGetComponentForSourceViewer
+    );
   }
 
   async getStandards(): Promise<Standards> {
@@ -65,7 +188,63 @@ export default class IssuesServiceMock {
     this.isAdmin = isAdmin;
   }
 
-  listHandler = (query: RequestData): Promise<RawIssuesResponse> => {
+  handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
+    return this.reply(range(data.from || 1, data.to || 10).map(line => mockSourceLine({ line })));
+  };
+
+  handleGetComponentForSourceViewer = (data: { component: string } & BranchParameters) => {
+    const file = this.sourceViewerFiles.find(f => f.key === data.component);
+    if (file === undefined) {
+      return Promise.reject({
+        errors: [{ msg: `No source file has been found for id ${data.component}` }]
+      });
+    }
+
+    return this.reply(file);
+  };
+
+  handleGetIssueFlowSnippets = (issueKey: string): Promise<Dict<SnippetsByComponent>> => {
+    const issue = this.list.find(i => i.issue.key === issueKey);
+    if (issue === undefined) {
+      return Promise.reject({ errors: [{ msg: `No issue has been found for id ${issueKey}` }] });
+    }
+    return this.reply(issue.snippets);
+  };
+
+  handleGetRuleDetails = (parameters: {
+    actives?: boolean;
+    key: string;
+  }): Promise<{ actives?: RuleActivation[]; rule: RuleDetails }> => {
+    if (parameters.key === 'advancedRuleId') {
+      return this.reply({
+        rule: mockRuleDetails({
+          key: parameters.key,
+          name: 'Advanced rule',
+          descriptionSections: [
+            { key: RuleDescriptionSections.INTRODUCTION, content: '<h1>Into</h1>' },
+            { key: RuleDescriptionSections.ROOT_CAUSE, content: '<h1>Because</h1>' },
+            { key: RuleDescriptionSections.HOW_TO_FIX, content: '<h1>Fix with</h1>' },
+            { key: RuleDescriptionSections.RESOURCES, content: '<h1>Link</h1>' }
+          ]
+        })
+      });
+    }
+    return this.reply({
+      rule: mockRuleDetails({
+        key: parameters.key,
+        name: 'Simple rule',
+        htmlNote: '<h1>Note</h1>',
+        descriptionSections: [
+          {
+            key: RuleDescriptionSections.DEFAULT,
+            content: '<h1>Default</h1> Default description'
+          }
+        ]
+      })
+    });
+  };
+
+  handleSearchIssues = (query: RequestData): Promise<RawIssuesResponse> => {
     const facets = query.facets.split(',').map((name: string) => {
       if (name === 'owaspTop10-2021') {
         return this.owasp2021FacetList();
@@ -79,7 +258,7 @@ export default class IssuesServiceMock {
       components: [mockReferenceComponent()],
       effortTotal: 199629,
       facets,
-      issues: [],
+      issues: this.list.map(line => line.issue),
       languages: [],
       paging: mockPaging()
     });
index 636c304c0d64f6b4ae9d6aad39140b81e9bb4347..7179d0f26e0adf1051ebc22fddb5e32ab40651c2 100644 (file)
@@ -26,7 +26,7 @@ import {
   getDuplications,
   getSources
 } from '../../api/components';
-import { mockSourceLine } from '../../helpers/testMocks';
+import { mockSourceLine } from '../../helpers/mocks/sources';
 import { BranchParameters } from '../../types/branch-like';
 import { Dict } from '../../types/types';
 
index d00bb73d453d1997e4f5bf98e54afe2ab05a0d20..a5cdd29cc530ed2d972ecafd4478f87e5a203cbe 100644 (file)
@@ -31,7 +31,7 @@ exports[`should render correctly: loaded 1`] = `
           "defaultRemFnType": "CONSTANT_ISSUE",
           "descriptionSections": Array [
             Object {
-              "content": "<b>Why<b/> Because",
+              "content": "<b>Why</b> Because",
               "key": "root_cause",
             },
           ],
@@ -73,7 +73,7 @@ exports[`should render correctly: loaded 1`] = `
           "defaultRemFnType": "CONSTANT_ISSUE",
           "descriptionSections": Array [
             Object {
-              "content": "<b>Why<b/> Because",
+              "content": "<b>Why</b> Because",
               "key": "root_cause",
             },
           ],
@@ -146,7 +146,7 @@ exports[`should render correctly: loaded 1`] = `
           "defaultRemFnType": "CONSTANT_ISSUE",
           "descriptionSections": Array [
             Object {
-              "content": "<b>Why<b/> Because",
+              "content": "<b>Why</b> Because",
               "key": "root_cause",
             },
           ],
@@ -187,7 +187,7 @@ exports[`should render correctly: loaded 1`] = `
           "defaultRemFnType": "CONSTANT_ISSUE",
           "descriptionSections": Array [
             Object {
-              "content": "<b>Why<b/> Because",
+              "content": "<b>Why</b> Because",
               "key": "root_cause",
             },
           ],
index 0a99fb39b8fa178ad60499320f6d309381ef1bc9..8153bf395790558b392a26cb3e739b44d708d212 100644 (file)
@@ -25,14 +25,59 @@ import { renderComponentApp } from '../../../helpers/testReactTestingUtils';
 import AppContainer from '../components/AppContainer';
 
 jest.mock('../../../api/issues');
+jest.mock('../../../api/rules');
+jest.mock('../../../api/components');
 
 let handler: IssuesServiceMock;
 
-beforeAll(() => {
+beforeEach(() => {
+  window.scrollTo = jest.fn();
   handler = new IssuesServiceMock();
 });
 
-afterEach(() => handler.reset());
+it('should open issue and navigate', async () => {
+  const user = userEvent.setup();
+  renderIssueApp();
+  expect(await screen.findByRole('region', { name: 'Fix that' })).toBeInTheDocument();
+  await user.click(screen.getByRole('region', { name: 'Fix that' }));
+  expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument();
+  expect(screen.getByRole('link', { name: 'advancedRuleId' })).toBeInTheDocument();
+
+  expect(screen.getByRole('button', { name: `issue.tabs.resources` })).toBeInTheDocument();
+  await user.click(screen.getByRole('button', { name: `issue.tabs.resources` }));
+  expect(screen.getByRole('heading', { name: 'Link' })).toBeInTheDocument();
+
+  expect(screen.getByRole('button', { name: `issue.tabs.how` })).toBeInTheDocument();
+  await user.click(screen.getByRole('button', { name: `issue.tabs.how` }));
+  expect(screen.getByRole('heading', { name: 'Fix with' })).toBeInTheDocument();
+
+  expect(screen.getByRole('button', { name: `issue.tabs.why` })).toBeInTheDocument();
+  await user.click(screen.getByRole('button', { name: `issue.tabs.why` }));
+  expect(screen.getByRole('heading', { name: 'Because' })).toBeInTheDocument();
+
+  await user.keyboard('{ArrowUp}');
+
+  expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument();
+  expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+
+  expect(screen.getByRole('button', { name: `issue.tabs.why` })).toBeInTheDocument();
+  await user.click(screen.getByRole('button', { name: `issue.tabs.why` }));
+  expect(screen.getByRole('heading', { name: 'Default' })).toBeInTheDocument();
+
+  await user.keyboard('{ArrowUp}');
+
+  expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument();
+  expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+
+  expect(screen.getByRole('button', { name: `issue.tabs.code` })).toBeInTheDocument();
+  await user.click(screen.getByRole('button', { name: `issue.tabs.code` }));
+  expect(screen.getByRole('region', { name: 'Issue on file' })).toBeInTheDocument();
+  expect(
+    screen.getByRole('row', {
+      name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;'
+    })
+  ).toBeInTheDocument();
+});
 
 it('should support OWASP Top 10 version 2021', async () => {
   const user = userEvent.setup();
index 676c7701ec6a63f001bc99ab0a5eb1b2f190372c..2d20f25c5acff9941818183f1fc954e82d830977 100644 (file)
@@ -38,6 +38,7 @@ import { WorkspaceContext } from '../../../components/workspace/context';
 import { getBranchLikeQuery } from '../../../helpers/branch-like';
 import { throwGlobalError } from '../../../helpers/error';
 import { translate } from '../../../helpers/l10n';
+import { HttpStatus } from '../../../helpers/request';
 import { BranchLike } from '../../../types/branch-like';
 import { isFile } from '../../../types/component';
 import {
@@ -151,11 +152,11 @@ export default class CrossComponentSourceViewerWrapper extends React.PureCompone
       }
     } catch (response) {
       const rsp = response as Response;
-      if (rsp.status !== 403) {
+      if (rsp.status !== HttpStatus.Forbidden) {
         throwGlobalError(response);
       }
       if (this.mounted) {
-        this.setState({ loading: false, notAccessible: rsp.status === 403 });
+        this.setState({ loading: false, notAccessible: rsp.status === HttpStatus.Forbidden });
       }
     }
   }
index 5db19b60cc0540a0c5b150576492e6ef5b648a7f..cbbe8669da51024f3d48da0b5bd0464b21d041d0 100644 (file)
@@ -24,12 +24,11 @@ import { getSources } from '../../../../api/components';
 import Issue from '../../../../components/issue/Issue';
 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
 import {
-  mockFlowLocation,
-  mockIssue,
   mockSnippetsByComponent,
   mockSourceLine,
   mockSourceViewerFile
-} from '../../../../helpers/testMocks';
+} from '../../../../helpers/mocks/sources';
+import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import { SnippetGroup } from '../../../../types/types';
 import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
@@ -50,6 +49,7 @@ it('should render correctly', () => {
 it('should render correctly with secondary locations', () => {
   // issue with secondary locations but no flows
   const issue = mockIssue(true, {
+    component: 'project:main.js',
     flows: [],
     textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
   });
@@ -65,7 +65,7 @@ it('should render correctly with secondary locations', () => {
         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
       })
     ],
-    ...mockSnippetsByComponent(issue.component, [
+    ...mockSnippetsByComponent('main.js', 'project', [
       ...range(2, 17),
       ...range(29, 39),
       ...range(69, 79)
@@ -81,6 +81,7 @@ it('should render correctly with secondary locations', () => {
 it('should render correctly with flows', () => {
   // issue with flows but no secondary locations
   const issue = mockIssue(true, {
+    component: 'project:main.js',
     secondaryLocations: [],
     textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
   });
@@ -96,7 +97,7 @@ it('should render correctly with flows', () => {
         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
       })
     ],
-    ...mockSnippetsByComponent(issue.component, [
+    ...mockSnippetsByComponent('main.js', 'project', [
       ...range(2, 17),
       ...range(29, 39),
       ...range(69, 79)
@@ -129,6 +130,7 @@ it('should render correctly with flows', () => {
 it('should render file-level issue correctly', () => {
   // issue with secondary locations and no primary location
   const issue = mockIssue(true, {
+    component: 'project:main.js',
     flows: [],
     textRange: undefined
   });
@@ -142,7 +144,7 @@ it('should render file-level issue correctly', () => {
           textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
         })
       ],
-      ...mockSnippetsByComponent(issue.component, range(29, 39))
+      ...mockSnippetsByComponent('main.js', 'project', range(29, 39))
     }
   });
 
@@ -151,7 +153,7 @@ it('should render file-level issue correctly', () => {
 
 it('should expand block', async () => {
   (getSources as jest.Mock).mockResolvedValueOnce(
-    Object.values(mockSnippetsByComponent('a', range(6, 59)).sources)
+    Object.values(mockSnippetsByComponent('a', 'project', range(6, 59)).sources)
   );
   const issue = mockIssue(true, {
     textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 }
@@ -167,7 +169,7 @@ it('should expand block', async () => {
         textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 }
       })
     ],
-    ...mockSnippetsByComponent('a', [...range(69, 83), ...range(102, 112)])
+    ...mockSnippetsByComponent('a', 'project', [...range(69, 83), ...range(102, 112)])
   };
 
   const wrapper = shallowRender({ issue, snippetGroup });
@@ -175,7 +177,7 @@ it('should expand block', async () => {
   wrapper.instance().expandBlock(0, 'up');
   await waitAndUpdate(wrapper);
 
-  expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'a', to: 68 });
+  expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'project:a', to: 68 });
   expect(wrapper.state('snippets')).toHaveLength(2);
   expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 });
   expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53);
@@ -183,7 +185,7 @@ it('should expand block', async () => {
 
 it('should expand full component', async () => {
   (getSources as jest.Mock).mockResolvedValueOnce(
-    Object.values(mockSnippetsByComponent('a', times(14)).sources)
+    Object.values(mockSnippetsByComponent('a', 'project', times(14)).sources)
   );
   const snippetGroup: SnippetGroup = {
     locations: [
@@ -196,7 +198,7 @@ it('should expand full component', async () => {
         textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 }
       })
     ],
-    ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
+    ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
   };
 
   const wrapper = shallowRender({ snippetGroup });
@@ -204,7 +206,7 @@ it('should expand full component', async () => {
   wrapper.instance().expandComponent();
   await waitAndUpdate(wrapper);
 
-  expect(getSources).toHaveBeenCalledWith({ key: 'a' });
+  expect(getSources).toHaveBeenCalledWith({ key: 'project:a' });
   expect(wrapper.state('snippets')).toHaveLength(1);
   expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 });
 });
@@ -212,12 +214,13 @@ it('should expand full component', async () => {
 it('should get the right branch when expanding', async () => {
   (getSources as jest.Mock).mockResolvedValueOnce(
     Object.values(
-      mockSnippetsByComponent('a', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]).sources
+      mockSnippetsByComponent('a', 'project', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
+        .sources
     )
   );
   const snippetGroup: SnippetGroup = {
     locations: [mockFlowLocation()],
-    ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 6, 7])
+    ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 6, 7])
   };
 
   const wrapper = shallowRender({
@@ -228,7 +231,7 @@ it('should get the right branch when expanding', async () => {
   wrapper.instance().expandBlock(0, 'down');
   await waitAndUpdate(wrapper);
 
-  expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'a', to: 67 });
+  expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'project:a', to: 67 });
 });
 
 it('should handle correctly open/close issue', () => {
@@ -254,15 +257,15 @@ it('should correctly handle lines actions', () => {
   const snippetGroup: SnippetGroup = {
     locations: [
       mockFlowLocation({
-        component: 'a',
+        component: 'my-project:foo/bar.ts',
         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
       }),
       mockFlowLocation({
-        component: 'a',
+        component: 'my-project:foo/bar.ts',
         textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
       })
     ],
-    ...mockSnippetsByComponent('a', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
+    ...mockSnippetsByComponent('foo/bar.ts', 'my-project', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
   };
   const loadDuplications = jest.fn();
   const renderDuplicationPopup = jest.fn();
@@ -278,14 +281,14 @@ it('should correctly handle lines actions', () => {
     .find('SnippetViewer')
     .first()
     .prop<Function>('loadDuplications')(line);
-  expect(loadDuplications).toHaveBeenCalledWith('a', line);
+  expect(loadDuplications).toHaveBeenCalledWith('my-project:foo/bar.ts', line);
 
   wrapper
     .find('SnippetViewer')
     .first()
     .prop<Function>('renderDuplicationPopup')(1, 13);
   expect(renderDuplicationPopup).toHaveBeenCalledWith(
-    mockSourceViewerFile({ key: 'a', path: 'a' }),
+    mockSourceViewerFile('foo/bar.ts', 'my-project'),
     1,
     13
   );
index bc2e28020796c45cc7706d79a8ce1d51eafe0e42..050f69d8df673ac832a116ef56dfe1125b87d360 100644 (file)
@@ -22,17 +22,16 @@ import * as React from 'react';
 import { getDuplications } from '../../../../api/components';
 import { getIssueFlowSnippets } from '../../../../api/issues';
 import {
-  mockFlowLocation,
-  mockIssue,
   mockSnippetsByComponent,
   mockSourceLine,
   mockSourceViewerFile
-} from '../../../../helpers/testMocks';
+} from '../../../../helpers/mocks/sources';
+import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import CrossComponentSourceViewerWrapper from '../CrossComponentSourceViewerWrapper';
 
 jest.mock('../../../../api/issues', () => {
-  const { mockSnippetsByComponent } = jest.requireActual('../../../../helpers/testMocks');
+  const { mockSnippetsByComponent } = jest.requireActual('../../../../helpers/mocks/sources');
   return {
     getIssueFlowSnippets: jest.fn().mockResolvedValue({ 'main.js': mockSnippetsByComponent() })
   };
index 76fb05d7c184ee68f41c1f18c275c06d29a890c9..579ca3c257bd0d65c4f04fcbe8f5d1598e5cadee 100644 (file)
@@ -20,8 +20,9 @@
 import { mount, shallow } from 'enzyme';
 import { range } from 'lodash';
 import * as React from 'react';
+import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/mocks/sources';
 import { scrollHorizontally } from '../../../../helpers/scrolling';
-import { mockIssue, mockSourceLine, mockSourceViewerFile } from '../../../../helpers/testMocks';
+import { mockIssue } from '../../../../helpers/testMocks';
 import SnippetViewer from '../SnippetViewer';
 
 jest.mock('../../../../helpers/scrolling', () => ({
@@ -82,7 +83,7 @@ it('should render correctly when at the top of the file', () => {
 });
 
 it('should render correctly when at the bottom of the file', () => {
-  const component = mockSourceViewerFile({ measures: { lines: '14' } });
+  const component = mockSourceViewerFile('foo/bar.ts', 'my-project', { measures: { lines: '14' } });
   const snippet = range(10, 14).map(line => mockSourceLine({ line }));
   const wrapper = shallowRender({
     component,
index c98ca92f56fa0d6a79807b59deed61a46acba9da..c19827b9ef7d00eee3191867e812206c00911e98 100644 (file)
@@ -18,15 +18,19 @@ exports[`should render correctly 1`] = `
     onExpand={[Function]}
     sourceViewerFile={
       Object {
-        "key": "foo",
+        "canMarkAsFavorite": true,
+        "fav": false,
+        "key": "project:foo/bar.ts",
+        "longName": "foo/bar.ts",
         "measures": Object {
           "coverage": "85.2",
           "duplicationDensity": "1.0",
           "issues": "12",
           "lines": "56",
         },
+        "name": "foo/bar.ts",
         "path": "foo/bar.ts",
-        "project": "my-project",
+        "project": "project",
         "projectName": "MyProject",
         "q": "FIL",
         "uuid": "foo-bar",
index 1c7210b016865fd489d72b1663913ebf0e9f4918..7027b2e58a702f31c6bf3b1c0f90adf9385dbb80 100644 (file)
@@ -29,15 +29,19 @@ exports[`should render correctly 2`] = `
       Object {
         "branchLike": undefined,
         "file": Object {
-          "key": "main.js",
+          "canMarkAsFavorite": true,
+          "fav": false,
+          "key": "project:main.js",
+          "longName": "main.js",
           "measures": Object {
             "coverage": "85.2",
             "duplicationDensity": "1.0",
             "issues": "12",
             "lines": "56",
           },
+          "name": "main.js",
           "path": "main.js",
-          "project": "my-project",
+          "project": "project",
           "projectName": "MyProject",
           "q": "FIL",
           "uuid": "foo-bar",
@@ -47,7 +51,7 @@ exports[`should render correctly 2`] = `
   >
     <ComponentSourceSnippetGroupViewer
       duplicationsByLine={Object {}}
-      isLastOccurenceOfPrimaryComponent={true}
+      isLastOccurenceOfPrimaryComponent={false}
       issue={
         Object {
           "actions": Array [],
@@ -173,15 +177,19 @@ exports[`should render correctly 2`] = `
       snippetGroup={
         Object {
           "component": Object {
-            "key": "main.js",
+            "canMarkAsFavorite": true,
+            "fav": false,
+            "key": "project:main.js",
+            "longName": "main.js",
             "measures": Object {
               "coverage": "85.2",
               "duplicationDensity": "1.0",
               "issues": "12",
               "lines": "56",
             },
+            "name": "main.js",
             "path": "main.js",
-            "project": "my-project",
+            "project": "project",
             "projectName": "MyProject",
             "q": "FIL",
             "uuid": "foo-bar",
@@ -356,15 +364,19 @@ exports[`should render correctly: no component found 1`] = `
       Object {
         "branchLike": undefined,
         "file": Object {
-          "key": "main.js",
+          "canMarkAsFavorite": true,
+          "fav": false,
+          "key": "project:main.js",
+          "longName": "main.js",
           "measures": Object {
             "coverage": "85.2",
             "duplicationDensity": "1.0",
             "issues": "12",
             "lines": "56",
           },
+          "name": "main.js",
           "path": "main.js",
-          "project": "my-project",
+          "project": "project",
           "projectName": "MyProject",
           "q": "FIL",
           "uuid": "foo-bar",
@@ -500,15 +512,19 @@ exports[`should render correctly: no component found 1`] = `
       snippetGroup={
         Object {
           "component": Object {
-            "key": "main.js",
+            "canMarkAsFavorite": true,
+            "fav": false,
+            "key": "project:main.js",
+            "longName": "main.js",
             "measures": Object {
               "coverage": "85.2",
               "duplicationDensity": "1.0",
               "issues": "12",
               "lines": "56",
             },
+            "name": "main.js",
             "path": "main.js",
-            "project": "my-project",
+            "project": "project",
             "projectName": "MyProject",
             "q": "FIL",
             "uuid": "foo-bar",
index 0d1e378c2c48136e0dae10d84b7f778349e50004..dadf1c31958c76918f9b47cd31b911e65e79124e 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import {
-  mockFlowLocation,
-  mockIssue,
-  mockSnippetsByComponent
-} from '../../../../helpers/testMocks';
+import { mockSnippetsByComponent } from '../../../../helpers/mocks/sources';
+import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
 import { createSnippets, expandSnippet, groupLocationsByComponent } from '../utils';
 
 describe('groupLocationsByComponent', () => {
@@ -43,7 +40,20 @@ describe('groupLocationsByComponent', () => {
           textRange: { startLine: 24, startOffset: 1, endLine: 24, endOffset: 2 }
         })
       ],
-      { 'main.js': mockSnippetsByComponent('main.js', [14, 15, 16, 17, 18, 22, 23, 24, 25, 26]) }
+      {
+        'main.js': mockSnippetsByComponent('main.js', 'project', [
+          14,
+          15,
+          16,
+          17,
+          18,
+          22,
+          23,
+          24,
+          25,
+          26
+        ])
+      }
     );
 
     expect(results).toHaveLength(1);
@@ -67,15 +77,15 @@ describe('groupLocationsByComponent', () => {
         })
       ],
       {
-        'A.js': mockSnippetsByComponent('A.js', [13, 14, 15, 16, 17, 18]),
-        'B.js': mockSnippetsByComponent('B.js', [14, 15, 16, 17, 18])
+        'A.js': mockSnippetsByComponent('A.js', 'project', [13, 14, 15, 16, 17, 18]),
+        'B.js': mockSnippetsByComponent('B.js', 'project', [14, 15, 16, 17, 18])
       }
     );
 
     expect(results).toHaveLength(3);
-    expect(results[0].component.key).toBe('A.js');
-    expect(results[1].component.key).toBe('B.js');
-    expect(results[2].component.key).toBe('A.js');
+    expect(results[0].component.key).toBe('project:A.js');
+    expect(results[1].component.key).toBe('project:B.js');
+    expect(results[2].component.key).toBe('project:A.js');
     expect(results[0].locations).toHaveLength(1);
     expect(results[1].locations).toHaveLength(1);
     expect(results[2].locations).toHaveLength(1);
index 53ea02c8505ca6ee77d2987d3c457103ef6272dc..d984d488ec438d24552f4c6f0797af6ae3c3ed18 100644 (file)
@@ -24,7 +24,8 @@ import { getSources } from '../../../../api/components';
 import { mockBranch } from '../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import { mockHotspot, mockHotspotComponent } from '../../../../helpers/mocks/security-hotspots';
-import { mockFlowLocation, mockSourceLine } from '../../../../helpers/testMocks';
+import { mockSourceLine } from '../../../../helpers/mocks/sources';
+import { mockFlowLocation } from '../../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../../helpers/testUtils';
 import { ComponentQualifier } from '../../../../types/component';
 import HotspotSnippetContainer from '../HotspotSnippetContainer';
index 1e58285c233a8ba74681d29b88db8a3fdda53a4f..98aec410c5c4a1b32120bd54c2535f29917fc856 100644 (file)
@@ -22,8 +22,8 @@ import React, { RefObject } from 'react';
 import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
+import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/mocks/sources';
 import { scrollToElement } from '../../../../helpers/scrolling';
-import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/testMocks';
 import SnippetViewer from '../../../issues/crossComponentSourceViewer/SnippetViewer';
 import HotspotSnippetContainerRenderer, {
   animateExpansion,
index be77b3e2008f823cf27ac10f864c0e3a172f4141..a4ade78ebae4e2e330982842e08c7bbf4ef2f714 100644 (file)
@@ -402,15 +402,19 @@ exports[`should render correctly: with sourcelines 1`] = `
       <SnippetViewer
         component={
           Object {
-            "key": "foo",
+            "canMarkAsFavorite": true,
+            "fav": false,
+            "key": "project:foo/bar.ts",
+            "longName": "foo/bar.ts",
             "measures": Object {
               "coverage": "85.2",
               "duplicationDensity": "1.0",
               "issues": "12",
               "lines": "56",
             },
+            "name": "foo/bar.ts",
             "path": "foo/bar.ts",
-            "project": "my-project",
+            "project": "project",
             "projectName": "MyProject",
             "q": "FIL",
             "uuid": "foo-bar",
index 75a56e77b15a80be5df4228d4829e01318eb4900..66cb69fdb7ed640e1883de71f16a1d69f9173a73 100644 (file)
@@ -21,7 +21,8 @@ import { shallow } from 'enzyme';
 import * as React from 'react';
 import { getComponentData, getComponentForSourceViewer, getSources } from '../../../api/components';
 import { mockMainBranch } from '../../../helpers/mocks/branch-like';
-import { mockIssue, mockSourceLine, mockSourceViewerFile } from '../../../helpers/testMocks';
+import { mockSourceLine, mockSourceViewerFile } from '../../../helpers/mocks/sources';
+import { mockIssue } from '../../../helpers/testMocks';
 import { waitAndUpdate } from '../../../helpers/testUtils';
 import defaultLoadIssues from '../helpers/loadIssues';
 import SourceViewerBase from '../SourceViewerBase';
index df85f4381f596d5fc5a1107247feded032f8c9e8..746d7cd495b09f3b0272252e89ec46a6cb4905c3 100644 (file)
@@ -20,7 +20,8 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockBranch } from '../../../helpers/mocks/branch-like';
-import { mockIssue, mockSourceLine } from '../../../helpers/testMocks';
+import { mockSourceLine } from '../../../helpers/mocks/sources';
+import { mockIssue } from '../../../helpers/testMocks';
 import { MetricKey } from '../../../types/metrics';
 import Line from '../components/Line';
 import SourceViewerCode from '../SourceViewerCode';
index 2713a1e0ea4b7d2c6148527dda0d3640e7543c70..fea1c7eda902f0de09a01e1a8ee709b36ba53f5a 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockMainBranch } from '../../../helpers/mocks/branch-like';
-import { mockSourceViewerFile } from '../../../helpers/testMocks';
+import { mockSourceViewerFile } from '../../../helpers/mocks/sources';
 import { ComponentQualifier } from '../../../types/component';
 import { MetricKey } from '../../../types/metrics';
 import { Measure } from '../../../types/types';
@@ -34,7 +34,7 @@ it('should render correctly for a unit test', () => {
   expect(
     shallowRender({
       showMeasures: true,
-      sourceViewerFile: mockSourceViewerFile({
+      sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
         q: ComponentQualifier.TestFile,
         measures: { tests: '12' }
       })
index 82232a309b39d2297942e8f21c2e4a2996936d82..86113ca61a2f271901bb504302ff4b88f9b2ec48 100644 (file)
@@ -20,7 +20,7 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockMainBranch } from '../../../helpers/mocks/branch-like';
-import { mockSourceViewerFile } from '../../../helpers/testMocks';
+import { mockSourceViewerFile } from '../../../helpers/mocks/sources';
 import { ComponentQualifier } from '../../../types/component';
 import SourceViewerHeaderSlim, { Props } from '../SourceViewerHeaderSlim';
 
@@ -29,14 +29,21 @@ it('should render correctly', () => {
   expect(shallowRender({ linkToProject: false })).toMatchSnapshot('no link to project');
   expect(shallowRender({ displayProjectName: false })).toMatchSnapshot('no project name');
   expect(
-    shallowRender({ sourceViewerFile: mockSourceViewerFile({ q: ComponentQualifier.Project }) })
+    shallowRender({
+      sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
+        q: ComponentQualifier.Project
+      })
+    })
   ).toMatchSnapshot('project root');
 });
 
 it('should render correctly for subproject', () => {
   expect(
     shallowRender({
-      sourceViewerFile: mockSourceViewerFile({ subProject: 'foo', subProjectName: 'Foo' })
+      sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
+        subProject: 'foo',
+        subProjectName: 'Foo'
+      })
     })
   ).toMatchSnapshot();
 });
@@ -47,7 +54,7 @@ function shallowRender(props: Partial<Props> = {}) {
       branchLike={mockMainBranch()}
       expandable={true}
       onExpand={jest.fn()}
-      sourceViewerFile={mockSourceViewerFile()}
+      sourceViewerFile={mockSourceViewerFile('foo/bar.ts', 'my-project')}
       {...props}
     />
   );
index 62e0125c3dee51a8b1bea49b5d52734c55cb2c73..ef8cdb4d1ba61086f8fb889347498e66afb59a15 100644 (file)
@@ -11,16 +11,20 @@ exports[`should render correctly 1`] = `
         "name": "master",
       },
       "file": Object {
-        "key": "foo",
+        "canMarkAsFavorite": true,
+        "fav": false,
+        "key": "project:foo/bar.ts",
         "leakPeriodDate": "2018-06-20T17:12:19+0200",
+        "longName": "foo/bar.ts",
         "measures": Object {
           "coverage": "85.2",
           "duplicationDensity": "1.0",
           "issues": "12",
           "lines": "56",
         },
+        "name": "foo/bar.ts",
         "path": "foo/bar.ts",
-        "project": "my-project",
+        "project": "project",
         "projectName": "MyProject",
         "q": "FIL",
         "uuid": "foo-bar",
index eaf213f5dcf4a4d6803d384c6eb6cc171c1264da..58ecb2f6e0b08d99d1701102ec95025562bd6131 100644 (file)
@@ -15,7 +15,7 @@ exports[`should render correctly for a regular file 1`] = `
       >
         <a
           className="link-with-icon"
-          href="/dashboard?id=my-project"
+          href="/dashboard?id=project"
         >
           <QualifierIcon
             qualifier="TRK"
@@ -79,9 +79,9 @@ exports[`should render correctly for a regular file 1`] = `
               Object {
                 "pathname": "/code",
                 "query": Object {
-                  "id": "my-project",
+                  "id": "project",
                   "line": undefined,
-                  "selected": "foo",
+                  "selected": "project:foo/bar.ts",
                 },
               }
             }
@@ -101,7 +101,7 @@ exports[`should render correctly for a regular file 1`] = `
         <li>
           <a
             className="js-raw-source"
-            href="/api/sources/raw?key=foo"
+            href="/api/sources/raw?key=project%3Afoo%2Fbar.ts"
             rel="noopener noreferrer"
             target="_blank"
           >
@@ -220,7 +220,7 @@ exports[`should render correctly for a unit test 1`] = `
                 "query": Object {
                   "id": "my-project",
                   "line": undefined,
-                  "selected": "foo",
+                  "selected": "my-project:foo/bar.ts",
                 },
               }
             }
@@ -240,7 +240,7 @@ exports[`should render correctly for a unit test 1`] = `
         <li>
           <a
             className="js-raw-source"
-            href="/api/sources/raw?key=foo"
+            href="/api/sources/raw?key=my-project%3Afoo%2Fbar.ts"
             rel="noopener noreferrer"
             target="_blank"
           >
@@ -275,7 +275,7 @@ exports[`should render correctly if issue details are passed 1`] = `
       >
         <a
           className="link-with-icon"
-          href="/dashboard?id=my-project"
+          href="/dashboard?id=project"
         >
           <QualifierIcon
             qualifier="TRK"
@@ -381,7 +381,7 @@ exports[`should render correctly if issue details are passed 1`] = `
               "pathname": "/project/issues",
               "query": Object {
                 "files": "foo/bar.ts",
-                "id": "my-project",
+                "id": "project",
                 "resolved": "false",
                 "types": "BUG",
               },
@@ -412,7 +412,7 @@ exports[`should render correctly if issue details are passed 1`] = `
               "pathname": "/project/issues",
               "query": Object {
                 "files": "foo/bar.ts",
-                "id": "my-project",
+                "id": "project",
                 "resolved": "false",
                 "types": "VULNERABILITY",
               },
@@ -443,7 +443,7 @@ exports[`should render correctly if issue details are passed 1`] = `
               "pathname": "/project/issues",
               "query": Object {
                 "files": "foo/bar.ts",
-                "id": "my-project",
+                "id": "project",
                 "resolved": "false",
                 "types": "CODE_SMELL",
               },
@@ -474,7 +474,7 @@ exports[`should render correctly if issue details are passed 1`] = `
               "pathname": "/project/issues",
               "query": Object {
                 "files": "foo/bar.ts",
-                "id": "my-project",
+                "id": "project",
                 "resolved": "false",
                 "types": "SECURITY_HOTSPOT",
               },
@@ -512,9 +512,9 @@ exports[`should render correctly if issue details are passed 1`] = `
               Object {
                 "pathname": "/code",
                 "query": Object {
-                  "id": "my-project",
+                  "id": "project",
                   "line": undefined,
-                  "selected": "foo",
+                  "selected": "project:foo/bar.ts",
                 },
               }
             }
@@ -534,7 +534,7 @@ exports[`should render correctly if issue details are passed 1`] = `
         <li>
           <a
             className="js-raw-source"
-            href="/api/sources/raw?key=foo"
+            href="/api/sources/raw?key=project%3Afoo%2Fbar.ts"
             rel="noopener noreferrer"
             target="_blank"
           >
index cb3b97075cbc11b68d794083d83eca0d2b803fb2..82add319ebab4077606369fffae69e8e4933fcb3 100644 (file)
@@ -19,7 +19,8 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { mockIssue, mockSourceLine } from '../../../../helpers/testMocks';
+import { mockSourceLine } from '../../../../helpers/mocks/sources';
+import { mockIssue } from '../../../../helpers/testMocks';
 import Line from '../Line';
 
 it('should render correctly for last, new, and highlighted lines', () => {
index 8d15db4f70829bb66615c1497b15e3ee8164eb78..5339dfc982853b232e57ac7d6446b72e8093595c 100644 (file)
@@ -19,7 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
-import { mockSourceLine } from '../../../../helpers/testMocks';
+import { mockSourceLine } from '../../../../helpers/mocks/sources';
 import LineCode from '../LineCode';
 
 it('render code', () => {
index 8d803bbb1728b1ca57999ad68b2f4a6448b100fc..712221baa9327cf7d6031e776d04c996671b3381 100644 (file)
@@ -20,7 +20,8 @@
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { mockIssue, mockSourceLine } from '../../../../helpers/testMocks';
+import { mockSourceLine } from '../../../../helpers/mocks/sources';
+import { mockIssue } from '../../../../helpers/testMocks';
 import LineIssuesList, { LineIssuesListProps } from '../LineIssuesList';
 
 it('should render issues', () => {
index 5c1f9884ec58f2512dda8db0474947dfc3493752..265c7d58e14d9f5574b2b26b297ade5dd2ac0502 100644 (file)
@@ -17,7 +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 { mockFlowLocation, mockSourceLine } from '../../../../helpers/testMocks';
+import { mockSourceLine } from '../../../../helpers/mocks/sources';
+import { mockFlowLocation } from '../../../../helpers/testMocks';
 import { getLinearLocations, getSecondaryIssueLocationsForLine } from '../issueLocations';
 
 describe('getSecondaryIssueLocationsForLine', () => {
diff --git a/server/sonar-web/src/main/js/helpers/mocks/sources.ts b/server/sonar-web/src/main/js/helpers/mocks/sources.ts
new file mode 100644 (file)
index 0000000..72523c2
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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 { ComponentQualifier } from '../../types/component';
+import { SnippetsByComponent, SourceLine, SourceViewerFile } from '../../types/types';
+
+export function mockSourceViewerFile(
+  name = 'foo/bar.ts',
+  project = 'project',
+  override?: Partial<SourceViewerFile>
+): SourceViewerFile {
+  return {
+    measures: {
+      coverage: '85.2',
+      duplicationDensity: '1.0',
+      issues: '12',
+      lines: '56'
+    },
+    project,
+    projectName: 'MyProject',
+    q: ComponentQualifier.File,
+    uuid: 'foo-bar',
+    key: `${project}:${name}`,
+    path: name,
+    name,
+    longName: name,
+    fav: false,
+    canMarkAsFavorite: true,
+    ...override
+  };
+}
+
+export function mockSourceLine(overrides: Partial<SourceLine> = {}): SourceLine {
+  return {
+    line: 16,
+    code: '<span class="k">import</span> java.util.<span class="sym-9 sym">ArrayList</span>;',
+    coverageStatus: 'covered',
+    coveredConditions: 2,
+    scmRevision: '80f564becc0c0a1c9abaa006eca83a4fd278c3f0',
+    scmAuthor: 'simon.brandhof@sonarsource.com',
+    scmDate: '2018-12-11T10:48:39+0100',
+    duplicated: false,
+    isNew: true,
+    ...overrides
+  };
+}
+
+export function mockSnippetsByComponent(
+  file = 'main.js',
+  project = 'project',
+  lines: number[] = [16]
+): SnippetsByComponent {
+  const sources = lines.reduce((lines: { [key: number]: SourceLine }, line) => {
+    lines[line] = mockSourceLine({ line });
+    return lines;
+  }, {});
+  return {
+    component: mockSourceViewerFile(file, project),
+    sources
+  };
+}
index dd4270c4c43b38bf03c319facbabbf457aa430d1..80bab8e94d6408cdbd661aa3486a158a3051beb8 100644 (file)
@@ -23,9 +23,8 @@ import { DocumentationEntry } from '../apps/documentation/utils';
 import { Exporter, Profile } from '../apps/quality-profiles/types';
 import { AppState } from '../types/appstate';
 import { RuleRepository } from '../types/coding-rules';
-import { ComponentQualifier } from '../types/component';
 import { EditionKey } from '../types/editions';
-import { RawIssue } from '../types/issues';
+import { IssueType, RawIssue } from '../types/issues';
 import { Language } from '../types/languages';
 import { DumpStatus, DumpTask } from '../types/project-dump';
 import { TaskStatuses } from '../types/tasks';
@@ -51,9 +50,6 @@ import {
   RuleDescriptionSections,
   RuleDetails,
   RuleParameter,
-  SnippetsByComponent,
-  SourceLine,
-  SourceViewerFile,
   SysInfoBase,
   SysInfoCluster,
   SysInfoStandalone
@@ -301,38 +297,6 @@ export function mockCondition(overrides: Partial<Condition> = {}): Condition {
   };
 }
 
-export function mockSnippetsByComponent(
-  component = 'main.js',
-  lines: number[] = [16]
-): SnippetsByComponent {
-  const sources = lines.reduce((lines: { [key: number]: SourceLine }, line) => {
-    lines[line] = mockSourceLine({ line });
-    return lines;
-  }, {});
-  return {
-    component: mockSourceViewerFile({
-      key: component,
-      path: component
-    }),
-    sources
-  };
-}
-
-export function mockSourceLine(overrides: Partial<SourceLine> = {}): SourceLine {
-  return {
-    line: 16,
-    code: '<span class="k">import</span> java.util.<span class="sym-9 sym">ArrayList</span>;',
-    coverageStatus: 'covered',
-    coveredConditions: 2,
-    scmRevision: '80f564becc0c0a1c9abaa006eca83a4fd278c3f0',
-    scmAuthor: 'simon.brandhof@sonarsource.com',
-    scmDate: '2018-12-11T10:48:39+0100',
-    duplicated: false,
-    isNew: true,
-    ...overrides
-  };
-}
-
 export function mockCurrentUser(overrides: Partial<CurrentUser> = {}): CurrentUser {
   return {
     isLoggedIn: false,
@@ -372,6 +336,7 @@ export function mockEvent(overrides = {}) {
 
 export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue> = {}): RawIssue {
   const rawIssue: RawIssue = {
+    actions: [],
     component: 'main.js',
     key: 'AVsae-CQS-9G3txfbFN2',
     line: 25,
@@ -380,13 +345,21 @@ export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue>
     severity: 'MAJOR',
     status: 'OPEN',
     textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
+    type: IssueType.CodeSmell,
     ...overrides
   };
 
   if (withLocations) {
     const loc = mockFlowLocation;
 
-    rawIssue.flows = [{ locations: [loc(), loc()] }];
+    rawIssue.flows = [
+      {
+        locations: [
+          loc({ component: overrides.component }),
+          loc({ component: overrides.component })
+        ]
+      }
+    ];
   }
 
   return {
@@ -608,7 +581,7 @@ export function mockRuleDetails(overrides: Partial<RuleDetails> = {}): RuleDetai
     descriptionSections: [
       {
         key: RuleDescriptionSections.ROOT_CAUSE,
-        content: '<b>Why<b/> Because'
+        content: '<b>Why</b> Because'
       }
     ],
     htmlDesc: '',
@@ -648,24 +621,6 @@ export function mockRuleDetailsParameter(overrides: Partial<RuleParameter> = {})
   };
 }
 
-export function mockSourceViewerFile(overrides: Partial<SourceViewerFile> = {}): SourceViewerFile {
-  return {
-    key: 'foo',
-    measures: {
-      coverage: '85.2',
-      duplicationDensity: '1.0',
-      issues: '12',
-      lines: '56'
-    },
-    path: 'foo/bar.ts',
-    project: 'my-project',
-    projectName: 'MyProject',
-    q: ComponentQualifier.File,
-    uuid: 'foo-bar',
-    ...overrides
-  };
-}
-
 export function mockStandaloneSysInfo(overrides: Partial<any> = {}): SysInfoStandalone {
   const baseInfo = mockBaseSysInfo(overrides);
   return {
index 18f6e322f1922bff1bce150c9b603012c81aac8a..360e4e153121eaa47dd74de20f446bc90bc6f57a 100644 (file)
@@ -42,6 +42,7 @@ interface Comment {
 }
 
 export interface RawIssue {
+  actions: string[];
   assignee?: string;
   author?: string;
   comments?: Array<Comment>;
@@ -54,10 +55,12 @@ export interface RawIssue {
   line?: number;
   project: string;
   rule: string;
+  message?: string;
   severity: string;
   status: string;
   subProject?: string;
   textRange?: TextRange;
+  type: IssueType;
 }
 
 export interface IssueResponse {
index 6d85754086496ec747db94b24e9670ec7bc17feb..5691a852d8f9fd90d47bfd3481e55b475ceb69a8 100644 (file)
@@ -674,7 +674,10 @@ export interface SourceViewerFile {
     lines?: string;
     tests?: string;
   };
+  canMarkAsFavorite?: boolean;
   path: string;
+  name?: string;
+  longName?: string;
   project: string;
   projectName: string;
   q: ComponentQualifier;