]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18425 Lay groundwork to start sharing data between service mocks
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 26 May 2023 13:11:32 +0000 (15:11 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 30 May 2023 20:02:52 +0000 (20:02 +0000)
server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts
server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
server/sonar-web/src/main/js/api/mocks/data/components.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/mocks/data/ids.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/mocks/data/issues.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/mocks/data/rules.ts [new file with mode: 0644]

index df1995018423d40ba306b315d45c75dcf9b527b0..15573b1dbcff5b966f3cc0f8326e64f9dbd8a8eb 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, flatMap, map, pick, times } from 'lodash';
-import { mockComponent } from '../../helpers/mocks/component';
-import {
-  mockDuplicatedFile,
-  mockDuplication,
-  mockDuplicationBlock,
-  mockSourceLine,
-  mockSourceViewerFile,
-} from '../../helpers/mocks/sources';
+import { cloneDeep, flatMap, map, pick } from 'lodash';
 import { HttpStatus, RequestData } from '../../helpers/request';
 import { BranchParameters } from '../../types/branch-like';
-import { ComponentQualifier, TreeComponent, Visibility } from '../../types/component';
+import { TreeComponent, Visibility } from '../../types/component';
 import {
   Component,
   ComponentMeasure,
   Dict,
   DuplicatedFile,
   Duplication,
-  Measure,
   Metric,
   Paging,
-  SourceLine,
-  SourceViewerFile,
 } from '../../types/types';
 import {
   GetTreeParams,
@@ -52,22 +41,12 @@ import {
   getSources,
   getTree,
 } from '../components';
-
-interface ComponentTree {
-  component: Component;
-  ancestors: Component[];
-  measures?: Measure[];
-  children: ComponentTree[];
-}
-
-interface SourceFile {
-  component: SourceViewerFile;
-  lines: SourceLine[];
-  duplication?: {
-    duplications: Duplication[];
-    files: Dict<DuplicatedFile>;
-  };
-}
+import {
+  ComponentTree,
+  SourceFile,
+  mockFullComponentTree,
+  mockFullSourceViewerFileList,
+} from './data/components';
 
 function isLeaf(node: ComponentTree) {
   return node.children.length === 0;
@@ -100,290 +79,8 @@ export default class ComponentsServiceMock {
   sourceFiles: SourceFile[];
 
   constructor(components?: ComponentTree[], sourceFiles?: SourceFile[]) {
-    const baseComponent = mockComponent({
-      key: 'foo',
-      name: 'Foo',
-      breadcrumbs: [{ key: 'foo', name: 'Foo', qualifier: ComponentQualifier.Project }],
-    });
-    const folderComponent = mockComponent({
-      key: 'foo:folderA',
-      name: 'folderA',
-      path: 'folderA',
-      qualifier: ComponentQualifier.Directory,
-      breadcrumbs: [
-        ...baseComponent.breadcrumbs,
-        { key: 'foo:folderA', name: 'folderA', qualifier: ComponentQualifier.Directory },
-      ],
-    });
-    this.defaultComponents = components || [
-      {
-        component: baseComponent,
-        ancestors: [],
-        children: [
-          {
-            component: folderComponent,
-            ancestors: [baseComponent],
-            children: [
-              {
-                component: mockComponent({
-                  key: 'foo:folderA/out.tsx',
-                  name: 'out.tsx',
-                  path: 'folderA/out.tsx',
-                  qualifier: ComponentQualifier.File,
-                  breadcrumbs: [
-                    ...folderComponent.breadcrumbs,
-                    {
-                      key: 'foo:folderA/out.tsx',
-                      name: 'out.tsx',
-                      qualifier: ComponentQualifier.File,
-                    },
-                  ],
-                }),
-                ancestors: [baseComponent, folderComponent],
-                children: [],
-              },
-            ],
-          },
-          {
-            component: mockComponent({
-              key: 'foo:index.tsx',
-              name: 'index.tsx',
-              path: 'index.tsx',
-              qualifier: ComponentQualifier.File,
-              breadcrumbs: [
-                ...baseComponent.breadcrumbs,
-                { key: 'foo:index.tsx', name: 'index.tsx', qualifier: ComponentQualifier.File },
-              ],
-            }),
-            ancestors: [baseComponent],
-            children: [],
-          },
-          {
-            component: mockComponent({
-              key: 'foo:test1.js',
-              name: 'test1.js',
-              path: 'test1.js',
-              qualifier: ComponentQualifier.File,
-              breadcrumbs: [
-                ...baseComponent.breadcrumbs,
-                { key: 'foo:test1.js', name: 'test1.js', qualifier: ComponentQualifier.File },
-              ],
-            }),
-            ancestors: [baseComponent],
-            children: [],
-          },
-          {
-            component: mockComponent({
-              key: 'foo:test2.js',
-              name: 'test2.js',
-              path: 'test2.js',
-              qualifier: ComponentQualifier.File,
-              breadcrumbs: [
-                ...baseComponent.breadcrumbs,
-                { key: 'foo:test2.js', name: 'test2.js', qualifier: ComponentQualifier.File },
-              ],
-            }),
-            ancestors: [baseComponent],
-            children: [],
-          },
-          {
-            component: mockComponent({
-              key: 'foo:testSymb.tsx',
-              name: 'testSymb.tsx',
-              path: 'testSymb.tsx',
-              qualifier: ComponentQualifier.File,
-              breadcrumbs: [
-                ...baseComponent.breadcrumbs,
-                {
-                  key: 'foo:testSymb.tsx',
-                  name: 'testSymb.tsx',
-                  qualifier: ComponentQualifier.File,
-                },
-              ],
-            }),
-            ancestors: [baseComponent],
-            children: [],
-          },
-          {
-            component: mockComponent({
-              key: 'foo:empty.js',
-              name: 'empty.js',
-              path: 'empty.js',
-              qualifier: ComponentQualifier.File,
-              breadcrumbs: [
-                ...baseComponent.breadcrumbs,
-                { key: 'foo:empty.js', name: 'empty.js', qualifier: ComponentQualifier.File },
-              ],
-            }),
-            ancestors: [baseComponent],
-            children: [],
-          },
-          {
-            component: mockComponent({
-              key: 'foo:huge.js',
-              name: 'huge.js',
-              path: 'huge.js',
-              qualifier: ComponentQualifier.File,
-              breadcrumbs: [
-                ...baseComponent.breadcrumbs,
-                { key: 'foo:huge.js', name: 'huge.js', qualifier: ComponentQualifier.File },
-              ],
-            }),
-            ancestors: [baseComponent],
-            children: [],
-          },
-        ],
-      },
-    ];
-
-    this.defaultSourceFiles =
-      sourceFiles ||
-      ([
-        {
-          component: mockSourceViewerFile('index.tsx', 'foo'),
-          lines: times(50, (n) =>
-            mockSourceLine({
-              line: n,
-              code: 'function Test() {}',
-            })
-          ),
-        },
-        {
-          component: mockSourceViewerFile('folderA/out.tsx', 'foo'),
-          lines: times(50, (n) =>
-            mockSourceLine({
-              line: n,
-              code: 'function Test() {}',
-            })
-          ),
-        },
-        {
-          component: mockSourceViewerFile('test1.js', 'foo'),
-          lines: [
-            {
-              line: 1,
-              code: '\u003cspan class\u003d"cd"\u003e/*\u003c/span\u003e',
-              scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
-              scmAuthor: 'stas.vilchik@sonarsource.com',
-              scmDate: '2018-07-10T20:21:20+0200',
-              duplicated: false,
-              isNew: false,
-              lineHits: 1,
-              coveredConditions: 1,
-            },
-            {
-              line: 2,
-              code: '\u003cspan class\u003d"cd"\u003e * SonarQube\u003c/span\u003e',
-              scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
-              scmAuthor: 'stas.vilchik@sonarsource.com',
-              scmDate: '2018-07-10T20:21:20+0200',
-              duplicated: false,
-              isNew: false,
-              lineHits: 0,
-              conditions: 1,
-            },
-            {
-              line: 3,
-              code: '\u003cspan class\u003d"cd"\u003e * Copyright\u003c/span\u003e',
-              scmRevision: '89a3d21bc28f2fa6201b5e8b1185d5358481b3dd',
-              scmAuthor: 'pierre.guillot@sonarsource.com',
-              scmDate: '2022-01-28T21:03:07+0100',
-              duplicated: false,
-              isNew: false,
-              lineHits: 1,
-            },
-            {
-              line: 4,
-              code: '\u003cspan class\u003d"cd"\u003e * mailto:info AT sonarsource DOT com\u003c/span\u003e',
-              scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
-              scmAuthor: 'stas.vilchik@sonarsource.com',
-              duplicated: false,
-              isNew: false,
-              lineHits: 1,
-              conditions: 1,
-              coveredConditions: 1,
-            },
-            {
-              line: 5,
-              code: '\u003cspan class\u003d"cd"\u003e * 5\u003c/span\u003e',
-              scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
-              duplicated: false,
-              isNew: false,
-              lineHits: 2,
-              conditions: 2,
-              coveredConditions: 1,
-            },
-            {
-              line: 6,
-              code: '\u003cspan class\u003d"cd"\u003e * 6\u003c/span\u003e',
-              scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
-              duplicated: false,
-              isNew: false,
-              lineHits: 0,
-            },
-            {
-              line: 7,
-              code: '\u003cspan class\u003d"cd"\u003e * 7\u003c/span\u003e',
-              scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
-              duplicated: true,
-              isNew: true,
-            },
-            {
-              code: '\u003cspan class\u003d"cd"\u003e * This program is free software; you can redistribute it and/or\u003c/span\u003e',
-              scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
-              scmAuthor: 'stas.vilchik@sonarsource.com',
-              scmDate: '2018-07-10T20:21:20+0200',
-              duplicated: false,
-              isNew: false,
-            },
-          ],
-          duplication: {
-            duplications: [
-              mockDuplication({
-                blocks: [
-                  mockDuplicationBlock({ from: 7, size: 1, _ref: '1' }),
-                  mockDuplicationBlock({ from: 1, size: 1, _ref: '2' }),
-                ],
-              }),
-            ],
-            files: {
-              '1': mockDuplicatedFile({ key: 'foo:test1.js' }),
-              '2': mockDuplicatedFile({ key: 'foo:test2.js' }),
-            },
-          },
-        },
-        {
-          component: mockSourceViewerFile('test2.js', 'foo'),
-          lines: times(50, (n) =>
-            mockSourceLine({
-              line: n,
-              code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
-            })
-          ),
-        },
-        {
-          component: mockSourceViewerFile('empty.js', 'foo'),
-          lines: [],
-        },
-        {
-          component: mockSourceViewerFile('huge.js', 'foo'),
-          lines: times(200, (n) =>
-            mockSourceLine({
-              line: n,
-              code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
-            })
-          ),
-        },
-        {
-          component: mockSourceViewerFile('testSymb.tsx', 'foo'),
-          lines: times(20, (n) =>
-            mockSourceLine({
-              line: n,
-              code: '  <span class="sym-35 sym">symbole</span>',
-            })
-          ),
-        },
-      ] as SourceFile[]);
+    this.defaultComponents = components || [mockFullComponentTree()];
+    this.defaultSourceFiles = sourceFiles || mockFullSourceViewerFileList();
 
     this.components = cloneDeep(this.defaultComponents);
     this.sourceFiles = cloneDeep(this.defaultSourceFiles);
index d79208bedcda1e4a4a24304a200b466d8852bcb2..a39e74e6c6acbec1aea26a051b63658f90793d79 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, keyBy, times, uniqueId } from 'lodash';
+import { cloneDeep, uniqueId } from 'lodash';
 import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
 import { mockIssueChangelog } from '../../helpers/mocks/issues';
-import { mockSnippetsByComponent } from '../../helpers/mocks/sources';
 import { RequestData } from '../../helpers/request';
 import { getStandards } from '../../helpers/security-standard';
-import {
-  mockLoggedInUser,
-  mockPaging,
-  mockRawIssue,
-  mockRule,
-  mockRuleDetails,
-} from '../../helpers/testMocks';
+import { mockLoggedInUser, mockPaging, mockRuleDetails } from '../../helpers/testMocks';
 import { SearchRulesResponse } from '../../types/coding-rules';
 import {
   ASSIGNEE_ME,
-  IssueActions,
   IssueResolution,
-  IssueScope,
-  IssueSeverity,
   IssueStatus,
   IssueTransition,
   IssueType,
@@ -47,14 +37,7 @@ import {
 } from '../../types/issues';
 import { SearchRulesQuery } from '../../types/rules';
 import { Standards } from '../../types/security';
-import {
-  Dict,
-  FlowType,
-  Rule,
-  RuleActivation,
-  RuleDetails,
-  SnippetsByComponent,
-} from '../../types/types';
+import { Dict, Rule, RuleActivation, RuleDetails, SnippetsByComponent } from '../../types/types';
 import { LoggedInUser, NoticeType } from '../../types/users';
 import {
   addIssueComment,
@@ -73,6 +56,8 @@ import {
 } from '../issues';
 import { getRuleDetails, searchRules } from '../rules';
 import { dismissNotice, getCurrentUser, searchUsers } from '../users';
+import { mockIssuesList } from './data/issues';
+import { mockRuleList } from './data/rules';
 
 function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
   return {
@@ -112,369 +97,8 @@ export default class IssuesServiceMock {
 
   constructor() {
     this.currentUser = mockLoggedInUser();
-    this.defaultList = [
-      {
-        issue: mockRawIssue(false, {
-          key: 'issue101',
-          component: 'foo:test1.js',
-          creationDate: '2023-01-05T09:36:01+0100',
-          message: 'Issue with no location message',
-          type: IssueType.Vulnerability,
-          rule: 'simpleRuleId',
-          textRange: {
-            startLine: 10,
-            endLine: 10,
-            startOffset: 0,
-            endOffset: 2,
-          },
-          flows: [
-            {
-              locations: [
-                {
-                  component: 'foo:test1.js',
-                  textRange: {
-                    startLine: 1,
-                    endLine: 1,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-              ],
-            },
-            {
-              locations: [
-                {
-                  component: 'foo:test2.js',
-                  textRange: {
-                    startLine: 20,
-                    endLine: 20,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-              ],
-            },
-          ],
-          resolution: IssueResolution.WontFix,
-          scope: IssueScope.Main,
-          tags: ['tag0', 'tag1'],
-        }),
-        snippets: keyBy(
-          [
-            mockSnippetsByComponent(
-              'test1.js',
-              'foo',
-              times(40, (i) => i + 1)
-            ),
-            mockSnippetsByComponent(
-              'test2.js',
-              'foo',
-              times(40, (i) => i + 1)
-            ),
-          ],
-          'component.key'
-        ),
-      },
-      {
-        issue: mockRawIssue(false, {
-          key: 'issue11',
-          component: 'foo:test1.js',
-          creationDate: '2022-01-01T09:36:01+0100',
-          message: 'FlowIssue',
-          type: IssueType.CodeSmell,
-          severity: IssueSeverity.Minor,
-          rule: 'simpleRuleId',
-          textRange: {
-            startLine: 10,
-            endLine: 10,
-            startOffset: 0,
-            endOffset: 2,
-          },
-          flows: [
-            {
-              type: FlowType.DATA,
-              description: 'Backtracking 1',
-              locations: [
-                {
-                  component: 'foo:test1.js',
-                  msg: 'Data location 1',
-                  textRange: {
-                    startLine: 20,
-                    endLine: 20,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-                {
-                  component: 'foo:test1.js',
-                  msg: 'Data location 2',
-                  textRange: {
-                    startLine: 21,
-                    endLine: 21,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-              ],
-            },
-            {
-              type: FlowType.EXECUTION,
-              locations: [
-                {
-                  component: 'foo:test2.js',
-                  msg: 'Execution location 1',
-                  textRange: {
-                    startLine: 20,
-                    endLine: 20,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-                {
-                  component: 'foo:test2.js',
-                  msg: 'Execution location 2',
-                  textRange: {
-                    startLine: 22,
-                    endLine: 22,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-                {
-                  component: 'foo:test2.js',
-                  msg: 'Execution location 3',
-                  textRange: {
-                    startLine: 5,
-                    endLine: 5,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-              ],
-            },
-          ],
-          tags: ['tag1'],
-        }),
-        snippets: keyBy(
-          [
-            mockSnippetsByComponent(
-              'test1.js',
-              'foo',
-              times(40, (i) => i + 1)
-            ),
-            mockSnippetsByComponent(
-              'test2.js',
-              'foo',
-              times(40, (i) => i + 1)
-            ),
-          ],
-          'component.key'
-        ),
-      },
-      {
-        issue: mockRawIssue(false, {
-          key: 'issue0',
-          component: 'foo:test1.js',
-          message: 'Issue on file',
-          assignee: mockLoggedInUser().login,
-          type: IssueType.CodeSmell,
-          rule: 'simpleRuleId',
-          textRange: undefined,
-          line: undefined,
-          scope: IssueScope.Test,
-        }),
-        snippets: {},
-      },
-      {
-        issue: mockRawIssue(false, {
-          key: 'issue1',
-          component: 'foo:huge.js',
-          message: 'Fix this',
-          type: IssueType.Vulnerability,
-          rule: 'simpleRuleId',
-          textRange: {
-            startLine: 10,
-            endLine: 10,
-            startOffset: 0,
-            endOffset: 2,
-          },
-          flows: [
-            {
-              locations: [
-                {
-                  component: 'foo:huge.js',
-                  msg: 'location 1',
-                  textRange: {
-                    startLine: 1,
-                    endLine: 1,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-              ],
-            },
-            {
-              locations: [
-                {
-                  component: 'foo:huge.js',
-                  msg: 'location 2',
-                  textRange: {
-                    startLine: 50,
-                    endLine: 50,
-                    startOffset: 0,
-                    endOffset: 1,
-                  },
-                },
-              ],
-            },
-          ],
-        }),
-        snippets: keyBy(
-          [
-            mockSnippetsByComponent(
-              'huge.js',
-              'foo',
-              times(80, (i) => i + 1)
-            ),
-            mockSnippetsByComponent(
-              'huge.js',
-              'foo',
-              times(80, (i) => i + 1)
-            ),
-          ],
-          'component.key'
-        ),
-      },
-      {
-        issue: mockRawIssue(false, {
-          actions: Object.values(IssueActions),
-          transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
-          key: 'issue2',
-          component: 'foo:test2.js',
-          message: 'Fix that',
-          rule: 'advancedRuleId',
-          textRange: {
-            startLine: 25,
-            endLine: 25,
-            startOffset: 0,
-            endOffset: 1,
-          },
-          ruleDescriptionContextKey: 'spring',
-          resolution: IssueResolution.Unresolved,
-          status: IssueStatus.Open,
-        }),
-        snippets: keyBy(
-          [
-            mockSnippetsByComponent(
-              'test2.js',
-              'foo',
-              times(40, (i) => i + 20)
-            ),
-          ],
-          'component.key'
-        ),
-      },
-      {
-        issue: mockRawIssue(false, {
-          key: 'issue3',
-          component: 'foo:test2.js',
-          message: 'Second issue',
-          rule: 'other',
-          textRange: {
-            startLine: 28,
-            endLine: 28,
-            startOffset: 0,
-            endOffset: 1,
-          },
-          resolution: IssueResolution.Fixed,
-          status: IssueStatus.Confirmed,
-        }),
-        snippets: keyBy(
-          [
-            mockSnippetsByComponent(
-              'test2.js',
-              'foo',
-              times(40, (i) => i + 20)
-            ),
-          ],
-          'component.key'
-        ),
-      },
-      {
-        issue: mockRawIssue(false, {
-          actions: Object.values(IssueActions),
-          transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
-          key: 'issue4',
-          component: 'foo:test2.js',
-          message: 'Issue with tags',
-          rule: 'other',
-          textRange: {
-            startLine: 25,
-            endLine: 25,
-            startOffset: 0,
-            endOffset: 1,
-          },
-          ruleDescriptionContextKey: 'spring',
-          ruleStatus: 'DEPRECATED',
-          quickFixAvailable: true,
-          tags: ['unused'],
-          codeVariants: ['variant 1', 'variant 2'],
-          project: 'org.project2',
-          assignee: 'email1@sonarsource.com',
-          author: 'email3@sonarsource.com',
-        }),
-        snippets: keyBy(
-          [
-            mockSnippetsByComponent(
-              'test2.js',
-              'foo',
-              times(40, (i) => i + 20)
-            ),
-          ],
-          'component.key'
-        ),
-      },
-      {
-        issue: mockRawIssue(false, {
-          key: 'issue1101',
-          component: 'foo:test5.js',
-          message: 'Issue on page 2',
-          rule: 'simpleRuleId',
-          textRange: undefined,
-          line: undefined,
-        }),
-        snippets: {},
-      },
-    ];
-    this.rulesList = [
-      mockRule({
-        key: 'simpleRuleId',
-        name: 'Simple rule',
-        lang: 'java',
-        langName: 'Java',
-        type: 'CODE_SMELL',
-      }),
-      mockRule({
-        key: 'advancedRuleId',
-        name: 'Advanced rule',
-        lang: 'web',
-        langName: 'HTML',
-        type: 'VULNERABILITY',
-      }),
-      mockRule({
-        key: 'cpp:S6069',
-        lang: 'cpp',
-        langName: 'C++',
-        name: 'Security hotspot rule',
-        type: 'SECURITY_HOTSPOT',
-      }),
-      mockRule({
-        key: 'tsql:S131',
-        name: '"CASE" expressions should end with "ELSE" clauses',
-        lang: 'tsql',
-        langName: 'T-SQL',
-      }),
-    ];
+    this.defaultList = mockIssuesList();
+    this.rulesList = mockRuleList();
 
     this.list = cloneDeep(this.defaultList);
 
diff --git a/server/sonar-web/src/main/js/api/mocks/data/components.ts b/server/sonar-web/src/main/js/api/mocks/data/components.ts
new file mode 100644 (file)
index 0000000..7549717
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { times } from 'lodash';
+import { isDiffMetric } from '../../../helpers/measures';
+import { mockComponent } from '../../../helpers/mocks/component';
+import {
+  mockDuplicatedFile,
+  mockDuplication,
+  mockDuplicationBlock,
+  mockSourceLine,
+  mockSourceViewerFile,
+} from '../../../helpers/mocks/sources';
+import { mockMeasure } from '../../../helpers/testMocks';
+import { ComponentQualifier } from '../../../types/component';
+import { MetricKey } from '../../../types/metrics';
+import {
+  Component,
+  Dict,
+  DuplicatedFile,
+  Duplication,
+  Measure,
+  SourceLine,
+  SourceViewerFile,
+} from '../../../types/types';
+import {
+  FILE1_KEY,
+  FILE2_KEY,
+  FILE3_KEY,
+  FILE4_KEY,
+  FILE5_KEY,
+  FILE6_KEY,
+  FILE7_KEY,
+  FOLDER1_KEY,
+  PARENT_COMPONENT_KEY,
+} from './ids';
+
+export interface ComponentTree {
+  component: Component;
+  ancestors: Component[];
+  measures?: Measure[];
+  children: ComponentTree[];
+}
+
+export interface SourceFile {
+  component: SourceViewerFile;
+  lines: SourceLine[];
+  duplication?: {
+    duplications: Duplication[];
+    files: Dict<DuplicatedFile>;
+  };
+}
+
+export function mockFullComponentTree(
+  baseComponent = mockComponent({
+    key: PARENT_COMPONENT_KEY,
+    name: 'Foo',
+    breadcrumbs: [
+      { key: PARENT_COMPONENT_KEY, name: 'Foo', qualifier: ComponentQualifier.Project },
+    ],
+  })
+): ComponentTree {
+  const folderComponent = mockComponent({
+    key: `${baseComponent.key}:${FOLDER1_KEY}`,
+    name: FOLDER1_KEY,
+    path: FOLDER1_KEY,
+    qualifier: ComponentQualifier.Directory,
+    breadcrumbs: [
+      ...baseComponent.breadcrumbs,
+      {
+        key: `${baseComponent.key}:${FOLDER1_KEY}`,
+        name: FOLDER1_KEY,
+        qualifier: ComponentQualifier.Directory,
+      },
+    ],
+  });
+  const measures = [
+    ...Object.values(MetricKey)
+      .filter((metric) => metric !== MetricKey.alert_status)
+      .map((metric) =>
+        isDiffMetric(metric)
+          ? mockMeasure({ metric, period: { index: 1, value: '1.0' } })
+          : mockMeasure({ metric, value: '2.0', period: undefined })
+      ),
+    mockMeasure({
+      metric: MetricKey.alert_status,
+      value: 'OK',
+      period: undefined,
+    }),
+  ];
+
+  return {
+    component: baseComponent,
+    ancestors: [],
+    measures,
+    children: [
+      {
+        component: folderComponent,
+        ancestors: [baseComponent],
+
+        measures,
+        children: [
+          {
+            component: mockComponent({
+              key: `${baseComponent.key}:${FOLDER1_KEY}/${FILE7_KEY}`,
+              name: FILE7_KEY,
+              path: `${FOLDER1_KEY}/${FILE7_KEY}`,
+              qualifier: ComponentQualifier.File,
+              breadcrumbs: [
+                ...folderComponent.breadcrumbs,
+                {
+                  key: `${baseComponent.key}:${FOLDER1_KEY}/${FILE7_KEY}`,
+                  name: FILE7_KEY,
+                  qualifier: ComponentQualifier.File,
+                },
+              ],
+            }),
+            ancestors: [baseComponent, folderComponent],
+
+            measures,
+            children: [],
+          },
+        ],
+      },
+      {
+        component: mockComponent({
+          key: `${baseComponent.key}:${FILE1_KEY}`,
+          name: FILE1_KEY,
+          path: FILE1_KEY,
+          qualifier: ComponentQualifier.File,
+          breadcrumbs: [
+            ...baseComponent.breadcrumbs,
+            {
+              key: `${baseComponent.key}:${FILE1_KEY}`,
+              name: FILE1_KEY,
+              qualifier: ComponentQualifier.File,
+            },
+          ],
+        }),
+        ancestors: [baseComponent],
+
+        measures,
+        children: [],
+      },
+      {
+        component: mockComponent({
+          key: `${baseComponent.key}:${FILE2_KEY}`,
+          name: FILE2_KEY,
+          path: FILE2_KEY,
+          qualifier: ComponentQualifier.File,
+          breadcrumbs: [
+            ...baseComponent.breadcrumbs,
+            {
+              key: `${baseComponent.key}:${FILE2_KEY}`,
+              name: FILE2_KEY,
+              qualifier: ComponentQualifier.File,
+            },
+          ],
+        }),
+        ancestors: [baseComponent],
+
+        measures,
+        children: [],
+      },
+      {
+        component: mockComponent({
+          key: `${baseComponent.key}:${FILE3_KEY}`,
+          name: FILE3_KEY,
+          path: FILE3_KEY,
+          qualifier: ComponentQualifier.File,
+          breadcrumbs: [
+            ...baseComponent.breadcrumbs,
+            {
+              key: `${baseComponent.key}:${FILE3_KEY}`,
+              name: FILE3_KEY,
+              qualifier: ComponentQualifier.File,
+            },
+          ],
+        }),
+        ancestors: [baseComponent],
+
+        measures,
+        children: [],
+      },
+      {
+        component: mockComponent({
+          key: `${baseComponent.key}:${FILE4_KEY}`,
+          name: FILE4_KEY,
+          path: FILE4_KEY,
+          qualifier: ComponentQualifier.File,
+          breadcrumbs: [
+            ...baseComponent.breadcrumbs,
+            {
+              key: `${baseComponent.key}:${FILE4_KEY}`,
+              name: FILE4_KEY,
+              qualifier: ComponentQualifier.File,
+            },
+          ],
+        }),
+        ancestors: [baseComponent],
+
+        measures,
+        children: [],
+      },
+      {
+        component: mockComponent({
+          key: `${baseComponent.key}:${FILE5_KEY}`,
+          name: FILE5_KEY,
+          path: FILE5_KEY,
+          qualifier: ComponentQualifier.File,
+          breadcrumbs: [
+            ...baseComponent.breadcrumbs,
+            {
+              key: `${baseComponent.key}:${FILE5_KEY}`,
+              name: FILE5_KEY,
+              qualifier: ComponentQualifier.File,
+            },
+          ],
+        }),
+        ancestors: [baseComponent],
+
+        measures,
+        children: [],
+      },
+      {
+        component: mockComponent({
+          key: `${baseComponent.key}:${FILE6_KEY}`,
+          name: FILE6_KEY,
+          path: FILE6_KEY,
+          qualifier: ComponentQualifier.File,
+          breadcrumbs: [
+            ...baseComponent.breadcrumbs,
+            {
+              key: `${baseComponent.key}:${FILE6_KEY}`,
+              name: FILE6_KEY,
+              qualifier: ComponentQualifier.File,
+            },
+          ],
+        }),
+        ancestors: [baseComponent],
+
+        measures,
+        children: [],
+      },
+    ],
+  };
+}
+
+export function mockFullSourceViewerFileList(baseComponentKey = PARENT_COMPONENT_KEY) {
+  return [
+    {
+      component: mockSourceViewerFile(FILE1_KEY, baseComponentKey),
+      lines: times(50, (n) =>
+        mockSourceLine({
+          line: n,
+          code: 'function Test() {}',
+        })
+      ),
+    },
+    {
+      component: mockSourceViewerFile(`${FOLDER1_KEY}/${FILE7_KEY}`, baseComponentKey),
+      lines: times(50, (n) =>
+        mockSourceLine({
+          line: n,
+          code: 'function Test() {}',
+        })
+      ),
+    },
+    {
+      component: mockSourceViewerFile(FILE2_KEY, baseComponentKey),
+      lines: [
+        {
+          line: 1,
+          code: '\u003cspan class\u003d"cd"\u003e/*\u003c/span\u003e',
+          scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+          scmAuthor: 'stas.vilchik@sonarsource.com',
+          scmDate: '2018-07-10T20:21:20+0200',
+          duplicated: false,
+          isNew: false,
+          lineHits: 1,
+          coveredConditions: 1,
+        },
+        {
+          line: 2,
+          code: '\u003cspan class\u003d"cd"\u003e * SonarQube\u003c/span\u003e',
+          scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+          scmAuthor: 'stas.vilchik@sonarsource.com',
+          scmDate: '2018-07-10T20:21:20+0200',
+          duplicated: false,
+          isNew: false,
+          lineHits: 0,
+          conditions: 1,
+        },
+        {
+          line: 3,
+          code: '\u003cspan class\u003d"cd"\u003e * Copyright\u003c/span\u003e',
+          scmRevision: '89a3d21bc28f2fa6201b5e8b1185d5358481b3dd',
+          scmAuthor: 'pierre.guillot@sonarsource.com',
+          scmDate: '2022-01-28T21:03:07+0100',
+          duplicated: false,
+          isNew: false,
+          lineHits: 1,
+        },
+        {
+          line: 4,
+          code: '\u003cspan class\u003d"cd"\u003e * mailto:info AT sonarsource DOT com\u003c/span\u003e',
+          scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+          scmAuthor: 'stas.vilchik@sonarsource.com',
+          duplicated: false,
+          isNew: false,
+          lineHits: 1,
+          conditions: 1,
+          coveredConditions: 1,
+        },
+        {
+          line: 5,
+          code: '\u003cspan class\u003d"cd"\u003e * 5\u003c/span\u003e',
+          scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
+          duplicated: false,
+          isNew: false,
+          lineHits: 2,
+          conditions: 2,
+          coveredConditions: 1,
+        },
+        {
+          line: 6,
+          code: '\u003cspan class\u003d"cd"\u003e * 6\u003c/span\u003e',
+          scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
+          duplicated: false,
+          isNew: false,
+          lineHits: 0,
+        },
+        {
+          line: 7,
+          code: '\u003cspan class\u003d"cd"\u003e * 7\u003c/span\u003e',
+          scmRevision: 'f04ee6b610528aa37b7b51be395c93524cebae8f',
+          duplicated: true,
+          isNew: true,
+        },
+        {
+          code: '\u003cspan class\u003d"cd"\u003e * This program is free software; you can redistribute it and/or\u003c/span\u003e',
+          scmRevision: 'f09ee6b610528aa37b7b51be395c93524cebae8f',
+          scmAuthor: 'stas.vilchik@sonarsource.com',
+          scmDate: '2018-07-10T20:21:20+0200',
+          duplicated: false,
+          isNew: false,
+        },
+      ],
+      duplication: {
+        duplications: [
+          mockDuplication({
+            blocks: [
+              mockDuplicationBlock({ from: 7, size: 1, _ref: '1' }),
+              mockDuplicationBlock({ from: 1, size: 1, _ref: '2' }),
+            ],
+          }),
+        ],
+        files: {
+          '1': mockDuplicatedFile({ key: `${baseComponentKey}:${FILE2_KEY}` }),
+          '2': mockDuplicatedFile({ key: `${baseComponentKey}:${FILE3_KEY}` }),
+        },
+      },
+    },
+    {
+      component: mockSourceViewerFile(FILE3_KEY, baseComponentKey),
+      lines: times(50, (n) =>
+        mockSourceLine({
+          line: n,
+          code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
+        })
+      ),
+    },
+    {
+      component: mockSourceViewerFile(FILE5_KEY, baseComponentKey),
+      lines: [],
+    },
+    {
+      component: mockSourceViewerFile(FILE6_KEY, baseComponentKey),
+      lines: times(200, (n) =>
+        mockSourceLine({
+          line: n,
+          code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`,
+        })
+      ),
+    },
+    {
+      component: mockSourceViewerFile(FILE4_KEY, baseComponentKey),
+      lines: times(20, (n) =>
+        mockSourceLine({
+          line: n,
+          code: '  <span class="sym-35 sym">symbole</span>',
+        })
+      ),
+    },
+  ] as SourceFile[];
+}
diff --git a/server/sonar-web/src/main/js/api/mocks/data/ids.ts b/server/sonar-web/src/main/js/api/mocks/data/ids.ts
new file mode 100644 (file)
index 0000000..38b8351
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.
+ */
+
+// Component tree.
+export const PARENT_COMPONENT_KEY = 'foo';
+//// Root folder.
+export const FILE1_KEY = 'index.tsx';
+export const FILE2_KEY = 'test1.js';
+export const FILE3_KEY = 'test2.js';
+export const FILE4_KEY = 'testSymb.tsx';
+export const FILE5_KEY = 'empty.js';
+export const FILE6_KEY = 'huge.js';
+export const FOLDER1_KEY = 'folderA';
+//// Inside folderA.
+export const FILE7_KEY = 'out.tsx';
+
+// Rules.
+export const SIMPLE_RULE = 'simpleRuleId';
+export const ADVANCED_RULE = 'advancedRuleId';
+export const S6069_RULE = 'cpp:S6069';
+export const S131_RULE = 'tsql:S131';
+
+// Issues.
+export const ISSUE_0 = 'issue0';
+export const ISSUE_1 = 'issue1';
+export const ISSUE_2 = 'issue2';
+export const ISSUE_3 = 'issue3';
+export const ISSUE_4 = 'issue4';
+export const ISSUE_11 = 'issue11';
+export const ISSUE_101 = 'issue101';
+export const ISSUE_1101 = 'issue1101';
+
+// Issue to rule map.
+export const ISSUE_TO_RULE = {
+  [ISSUE_0]: SIMPLE_RULE,
+  [ISSUE_1]: SIMPLE_RULE,
+  [ISSUE_2]: ADVANCED_RULE,
+  [ISSUE_3]: 'other',
+  [ISSUE_4]: 'other',
+  [ISSUE_11]: SIMPLE_RULE,
+  [ISSUE_101]: SIMPLE_RULE,
+  [ISSUE_1101]: SIMPLE_RULE,
+};
+
+// Issue to files map.
+export const ISSUE_TO_FILES = {
+  [ISSUE_0]: [FILE2_KEY],
+  [ISSUE_1]: [FILE6_KEY],
+  [ISSUE_2]: [FILE3_KEY],
+  [ISSUE_3]: [FILE3_KEY],
+  [ISSUE_4]: [FILE3_KEY],
+  [ISSUE_11]: [FILE2_KEY, FILE3_KEY],
+  [ISSUE_101]: [FILE2_KEY, FILE3_KEY],
+  [ISSUE_1101]: [FILE7_KEY],
+};
diff --git a/server/sonar-web/src/main/js/api/mocks/data/issues.ts b/server/sonar-web/src/main/js/api/mocks/data/issues.ts
new file mode 100644 (file)
index 0000000..86fe6aa
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { keyBy, times } from 'lodash';
+import { mockSnippetsByComponent } from '../../../helpers/mocks/sources';
+import { mockLoggedInUser, mockRawIssue } from '../../../helpers/testMocks';
+import {
+  IssueActions,
+  IssueResolution,
+  IssueScope,
+  IssueSeverity,
+  IssueStatus,
+  IssueType,
+} from '../../../types/issues';
+import { FlowType } from '../../../types/types';
+import {
+  ISSUE_0,
+  ISSUE_1,
+  ISSUE_101,
+  ISSUE_11,
+  ISSUE_1101,
+  ISSUE_2,
+  ISSUE_3,
+  ISSUE_4,
+  ISSUE_TO_FILES,
+  ISSUE_TO_RULE,
+  PARENT_COMPONENT_KEY,
+} from './ids';
+
+export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY) {
+  return [
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_101,
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_101][0]}`,
+        creationDate: '2023-01-05T09:36:01+0100',
+        message: 'Issue with no location message',
+        type: IssueType.Vulnerability,
+        rule: ISSUE_TO_RULE[ISSUE_101],
+        textRange: {
+          startLine: 10,
+          endLine: 10,
+          startOffset: 0,
+          endOffset: 2,
+        },
+        flows: [
+          {
+            locations: [
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_101][0]}`,
+                textRange: {
+                  startLine: 1,
+                  endLine: 1,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+            ],
+          },
+          {
+            locations: [
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_101][1]}`,
+                textRange: {
+                  startLine: 20,
+                  endLine: 20,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+            ],
+          },
+        ],
+        resolution: IssueResolution.WontFix,
+        scope: IssueScope.Main,
+        tags: ['tag0', 'tag1'],
+      }),
+      snippets: keyBy(
+        [
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_101][0],
+            baseComponentKey,
+            times(40, (i) => i + 1)
+          ),
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_101][1],
+            baseComponentKey,
+            times(40, (i) => i + 1)
+          ),
+        ],
+        'component.key'
+      ),
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_11,
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][0]}`,
+        creationDate: '2022-01-01T09:36:01+0100',
+        message: 'FlowIssue',
+        type: IssueType.CodeSmell,
+        severity: IssueSeverity.Minor,
+        rule: ISSUE_TO_RULE[ISSUE_11],
+        textRange: {
+          startLine: 10,
+          endLine: 10,
+          startOffset: 0,
+          endOffset: 2,
+        },
+        flows: [
+          {
+            type: FlowType.DATA,
+            description: 'Backtracking 1',
+            locations: [
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][0]}`,
+                msg: 'Data location 1',
+                textRange: {
+                  startLine: 20,
+                  endLine: 20,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][0]}`,
+                msg: 'Data location 2',
+                textRange: {
+                  startLine: 21,
+                  endLine: 21,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+            ],
+          },
+          {
+            type: FlowType.EXECUTION,
+            locations: [
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][1]}`,
+                msg: 'Execution location 1',
+                textRange: {
+                  startLine: 20,
+                  endLine: 20,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][1]}`,
+                msg: 'Execution location 2',
+                textRange: {
+                  startLine: 22,
+                  endLine: 22,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_11][1]}`,
+                msg: 'Execution location 3',
+                textRange: {
+                  startLine: 5,
+                  endLine: 5,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+            ],
+          },
+        ],
+        tags: ['tag1'],
+      }),
+      snippets: keyBy(
+        [
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_11][0],
+            baseComponentKey,
+            times(40, (i) => i + 1)
+          ),
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_11][1],
+            baseComponentKey,
+            times(40, (i) => i + 1)
+          ),
+        ],
+        'component.key'
+      ),
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_0,
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_0][0]}`,
+        message: 'Issue on file',
+        assignee: mockLoggedInUser().login,
+        type: IssueType.CodeSmell,
+        rule: ISSUE_TO_RULE[ISSUE_0],
+        textRange: undefined,
+        line: undefined,
+        scope: IssueScope.Test,
+      }),
+      snippets: {},
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_1,
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1][0]}`,
+        message: 'Fix this',
+        type: IssueType.Vulnerability,
+        rule: ISSUE_TO_RULE[ISSUE_1],
+        textRange: {
+          startLine: 10,
+          endLine: 10,
+          startOffset: 0,
+          endOffset: 2,
+        },
+        flows: [
+          {
+            locations: [
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1][0]}`,
+                msg: 'location 1',
+                textRange: {
+                  startLine: 1,
+                  endLine: 1,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+            ],
+          },
+          {
+            locations: [
+              {
+                component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1][0]}`,
+                msg: 'location 2',
+                textRange: {
+                  startLine: 50,
+                  endLine: 50,
+                  startOffset: 0,
+                  endOffset: 1,
+                },
+              },
+            ],
+          },
+        ],
+      }),
+      snippets: keyBy(
+        [
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_1][0],
+            baseComponentKey,
+            times(80, (i) => i + 1)
+          ),
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_1][0],
+            baseComponentKey,
+            times(80, (i) => i + 1)
+          ),
+        ],
+        'component.key'
+      ),
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_2,
+        actions: Object.values(IssueActions),
+        transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_2][0]}`,
+        message: 'Fix that',
+        rule: ISSUE_TO_RULE[ISSUE_2],
+        textRange: {
+          startLine: 25,
+          endLine: 25,
+          startOffset: 0,
+          endOffset: 1,
+        },
+        ruleDescriptionContextKey: 'spring',
+        resolution: IssueResolution.Unresolved,
+        status: IssueStatus.Open,
+      }),
+      snippets: keyBy(
+        [
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_2][0],
+            baseComponentKey,
+            times(40, (i) => i + 20)
+          ),
+        ],
+        'component.key'
+      ),
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_3,
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_3][0]}`,
+        message: 'Second issue',
+        rule: ISSUE_TO_RULE[ISSUE_3],
+        textRange: {
+          startLine: 28,
+          endLine: 28,
+          startOffset: 0,
+          endOffset: 1,
+        },
+        resolution: IssueResolution.Fixed,
+        status: IssueStatus.Confirmed,
+      }),
+      snippets: keyBy(
+        [
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_3][0],
+            baseComponentKey,
+            times(40, (i) => i + 20)
+          ),
+        ],
+        'component.key'
+      ),
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_4,
+        actions: Object.values(IssueActions),
+        transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_4][0]}`,
+        message: 'Issue with tags',
+        rule: ISSUE_TO_RULE[ISSUE_4],
+        textRange: {
+          startLine: 25,
+          endLine: 25,
+          startOffset: 0,
+          endOffset: 1,
+        },
+        ruleDescriptionContextKey: 'spring',
+        ruleStatus: 'DEPRECATED',
+        quickFixAvailable: true,
+        tags: ['unused'],
+        codeVariants: ['variant 1', 'variant 2'],
+        project: 'org.project2',
+        assignee: 'email1@sonarsource.com',
+        author: 'email3@sonarsource.com',
+      }),
+      snippets: keyBy(
+        [
+          mockSnippetsByComponent(
+            ISSUE_TO_FILES[ISSUE_4][0],
+            baseComponentKey,
+            times(40, (i) => i + 20)
+          ),
+        ],
+        'component.key'
+      ),
+    },
+    {
+      issue: mockRawIssue(false, {
+        key: ISSUE_1101,
+        component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1101][0]}`,
+        message: 'Issue on page 2',
+        rule: ISSUE_TO_RULE[ISSUE_1101],
+        textRange: undefined,
+        line: undefined,
+      }),
+      snippets: {},
+    },
+  ];
+}
diff --git a/server/sonar-web/src/main/js/api/mocks/data/rules.ts b/server/sonar-web/src/main/js/api/mocks/data/rules.ts
new file mode 100644 (file)
index 0000000..de3de9b
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { mockRule } from '../../../helpers/testMocks';
+import { ADVANCED_RULE, S131_RULE, S6069_RULE, SIMPLE_RULE } from './ids';
+
+export function mockRuleList() {
+  return [
+    mockRule({
+      key: SIMPLE_RULE,
+      name: 'Simple rule',
+      lang: 'java',
+      langName: 'Java',
+      type: 'CODE_SMELL',
+    }),
+    mockRule({
+      key: ADVANCED_RULE,
+      name: 'Advanced rule',
+      lang: 'web',
+      langName: 'HTML',
+      type: 'VULNERABILITY',
+    }),
+    mockRule({
+      key: S6069_RULE,
+      lang: 'cpp',
+      langName: 'C++',
+      name: 'Security hotspot rule',
+      type: 'SECURITY_HOTSPOT',
+    }),
+    mockRule({
+      key: S131_RULE,
+      name: '"CASE" expressions should end with "ELSE" clauses',
+      lang: 'tsql',
+      langName: 'T-SQL',
+    }),
+  ];
+}