diff options
Diffstat (limited to 'server/sonar-web/src/main/js/api')
5 files changed, 676 insertions, 519 deletions
diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index 5ed1fc90d08..aa9c228712d 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -140,7 +140,7 @@ export function getComponent( return getJSON('/api/measures/component', data); } -type GetTreeParams = { +export type GetTreeParams = { asc?: boolean; component: string; p?: number; diff --git a/server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts b/server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts deleted file mode 100644 index 2c9a8021110..00000000000 --- a/server/sonar-web/src/main/js/api/mocks/CodeServiceMocks.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* - * 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 { cloneDeep, flatMap, map } from 'lodash'; -import { mockComponent, mockComponentMeasure } from '../../helpers/mocks/component'; -import { ComponentQualifier } from '../../types/component'; -import { RawIssuesResponse } from '../../types/issues'; -import { ComponentMeasure, Metric, Paging } from '../../types/types'; -import { getChildren, getComponentTree } from '../components'; -import { searchIssues } from '../issues'; - -interface ComponentTree { - component: ComponentMeasure; - child: ComponentTree[]; -} - -function isLeaf(node: ComponentTree) { - return node.child.length === 0; -} - -function listChildComponent(node: ComponentTree): ComponentMeasure[] { - return map(node.child, (n) => n.component); -} - -function listAllComponent(node: ComponentTree): ComponentMeasure[] { - if (isLeaf(node)) { - return [node.component]; - } - - return [node.component, ...flatMap(node.child, listAllComponent)]; -} - -function listLeavesComponent(node: ComponentTree): ComponentMeasure[] { - if (isLeaf(node)) { - return [node.component]; - } - return flatMap(node.child, listLeavesComponent); -} - -export default class CodeServiceMock { - componentTree: ComponentTree; - - constructor() { - this.componentTree = { - component: mockComponentMeasure(), - child: [ - { - component: mockComponentMeasure(false, { - key: 'foo:folerA', - name: 'folderA', - path: 'folderA', - qualifier: ComponentQualifier.Directory, - }), - child: [ - { - component: mockComponentMeasure(true, { - key: 'foo:folderA/out.tsx', - name: 'out.tsx', - path: 'folderA/out.tsx', - }), - child: [], - }, - ], - }, - { - component: mockComponentMeasure(true, { - key: 'foo:index.tsx', - name: 'index.tsx', - path: 'index.tsx', - }), - child: [], - }, - ], - }; - (getComponentTree as jest.Mock).mockImplementation(this.handleGetComponentTree); - (getChildren as jest.Mock).mockImplementation(this.handleGetChildren); - (searchIssues as jest.Mock).mockImplementation(this.handleSearchIssue); - } - - findBaseComponent(key: string, from = this.componentTree): ComponentTree | undefined { - if (from.component.key === key) { - return from; - } - return from.child.find((node) => this.findBaseComponent(key, node)); - } - - handleGetChildren = ( - component: string - ): Promise<{ - baseComponent: ComponentMeasure; - components: ComponentMeasure[]; - metrics: Metric[]; - paging: Paging; - }> => { - return this.handleGetComponentTree('children', component); - }; - - handleGetComponentTree = ( - strategy: string, - component: string - ): Promise<{ - baseComponent: ComponentMeasure; - components: ComponentMeasure[]; - metrics: Metric[]; - paging: Paging; - }> => { - const base = this.findBaseComponent(component); - let components: ComponentMeasure[] = []; - if (base === undefined) { - return Promise.reject({ - errors: [{ msg: `No component has been found for id ${component}` }], - }); - } - if (strategy === 'all' || strategy === '') { - components = listAllComponent(base); - } else if (strategy === 'children') { - components = listChildComponent(base); - } else if (strategy === 'leaves') { - components = listLeavesComponent(base); - } - - return this.reply({ - baseComponent: base.component, - components, - metrics: [], - paging: { - pageIndex: 1, - pageSize: 100, - total: components.length, - }, - }); - }; - - handleSearchIssue = (): Promise<RawIssuesResponse> => { - return this.reply({ - components: [], - effortTotal: 1, - facets: [], - issues: [], - languages: [], - paging: { total: 0, pageIndex: 1, pageSize: 100 }, - }); - }; - - getRootComponent() { - return mockComponent(this.componentTree.component); - } - - reply<T>(response: T): Promise<T> { - return Promise.resolve(cloneDeep(response)); - } -} diff --git a/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts new file mode 100644 index 00000000000..8308107d00f --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts @@ -0,0 +1,631 @@ +/* + * 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 { cloneDeep, flatMap, map, pick, times } from 'lodash'; +import { mockComponent } from '../../helpers/mocks/component'; +import { + mockDuplicatedFile, + mockDuplication, + mockDuplicationBlock, + mockSourceLine, + mockSourceViewerFile, +} from '../../helpers/mocks/sources'; +import { HttpStatus, RequestData } from '../../helpers/request'; +import { BranchParameters } from '../../types/branch-like'; +import { ComponentQualifier, TreeComponent, Visibility } from '../../types/component'; +import { + Component, + ComponentMeasure, + Dict, + DuplicatedFile, + Duplication, + Measure, + Metric, + Paging, + SourceLine, + SourceViewerFile, +} from '../../types/types'; +import { + getChildren, + getComponentData, + getComponentForSourceViewer, + getComponentTree, + getDuplications, + getSources, + getTree, + GetTreeParams, +} from '../components'; + +interface ComponentTree { + component: Component; + ancestors: Component[]; + measures?: Measure[]; + children: ComponentTree[]; +} + +interface SourceFile { + component: SourceViewerFile; + lines: SourceLine[]; + duplication?: { + duplications: Duplication[]; + files: Dict<DuplicatedFile>; + }; +} + +function isLeaf(node: ComponentTree) { + return node.children.length === 0; +} + +function listChildComponent(node: ComponentTree): Component[] { + return map(node.children, (n) => n.component); +} + +function listAllComponent(node: ComponentTree): Component[] { + if (isLeaf(node)) { + return [node.component]; + } + + return [node.component, ...flatMap(node.children, listAllComponent)]; +} + +function listLeavesComponent(node: ComponentTree): Component[] { + if (isLeaf(node)) { + return [node.component]; + } + return flatMap(node.children, listLeavesComponent); +} + +export default class ComponentsServiceMock { + failLoadingComponentStatus: HttpStatus | undefined = undefined; + defaultComponents: ComponentTree[]; + components: ComponentTree[]; + defaultSourceFiles: SourceFile[]; + 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.components = cloneDeep(this.defaultComponents); + this.sourceFiles = cloneDeep(this.defaultSourceFiles); + + (getComponentTree as jest.Mock).mockImplementation(this.handleGetComponentTree); + (getChildren as jest.Mock).mockImplementation(this.handleGetChildren); + (getTree as jest.Mock).mockImplementation(this.handleGetTree); + (getComponentData as jest.Mock).mockImplementation(this.handleGetComponentData); + (getComponentForSourceViewer as jest.Mock).mockImplementation( + this.handleGetComponentForSourceViewer + ); + (getDuplications as jest.Mock).mockImplementation(this.handleGetDuplications); + (getSources as jest.Mock).mockImplementation(this.handleGetSources); + } + + findComponentTree = (key: string, from?: ComponentTree): ComponentTree | undefined => { + const recurse = (node: ComponentTree): ComponentTree | undefined => { + if (node.component.key === key) { + return node; + } + return node.children.find((child) => recurse(child)); + }; + + if (from === undefined) { + for (let i = 0, len = this.components.length; i < len; i++) { + const tree = recurse(this.components[i]); + if (tree) { + return tree; + } + } + throw new Error(`Couldn't find component tree for key ${key}`); + } + + return recurse(from); + }; + + findSourceFile = (key: string): SourceFile => { + const sourceFile = this.sourceFiles.find((s) => s.component.key === key); + if (sourceFile) { + return sourceFile; + } + throw new Error(`Couldn't find source file for key ${key}`); + }; + + registerComponent = (component: Component, ancestors: Component[] = []) => { + this.components.push({ component, ancestors, children: [] }); + }; + + registerComponentTree = (componentTree: ComponentTree, replace = true) => { + if (replace) { + this.components = []; + } + this.components.push(componentTree); + }; + + setFailLoadingComponentStatus = (status: HttpStatus.Forbidden | HttpStatus.NotFound) => { + this.failLoadingComponentStatus = status; + }; + + getHugeFileKey = () => { + const { sourceFile } = this.sourceFiles.reduce( + (acc, sourceFile) => { + if (sourceFile.lines.length > acc.size) { + return { + sourceFile, + size: sourceFile.lines.length, + }; + } + return acc; + }, + { sourceFile: undefined, size: -Infinity } + ); + if (sourceFile) { + return sourceFile.component.key; + } + throw new Error('Could not find a large source file'); + }; + + getEmptyFileKey = () => { + const sourceFile = this.sourceFiles.find((sourceFile) => { + if (sourceFile.lines.length === 0) { + return sourceFile; + } + }); + if (sourceFile) { + return sourceFile.component.key; + } + throw new Error('Could not find an empty source file'); + }; + + getNonEmptyFileKey = (preferredKey = 'foo:test1.js') => { + let sourceFile = this.sourceFiles.find((sourceFile) => { + if (sourceFile.component.key === preferredKey) { + return sourceFile; + } + }); + + if (!sourceFile) { + sourceFile = this.sourceFiles.find((sourceFile) => { + if (sourceFile.lines.length > 0) { + return sourceFile; + } + }); + } + + if (sourceFile) { + return sourceFile.component.key; + } + throw new Error('Could not find a non-empty source file'); + }; + + reset = () => { + this.components = cloneDeep(this.defaultComponents); + this.sourceFiles = cloneDeep(this.defaultSourceFiles); + }; + + handleGetChildren = ( + component: string, + metrics: string[] = [], + data: RequestData = {} + ): Promise<{ + baseComponent: ComponentMeasure; + components: ComponentMeasure[]; + metrics: Metric[]; + paging: Paging; + }> => { + return this.handleGetComponentTree('children', component, metrics, data); + }; + + handleGetComponentTree = ( + strategy: string, + key: string, + _metrics: string[] = [], + { p = 1, ps = 100 }: RequestData = {} + ): Promise<{ + baseComponent: ComponentMeasure; + components: ComponentMeasure[]; + metrics: Metric[]; + paging: Paging; + }> => { + const base = this.findComponentTree(key); + let components: Component[] = []; + if (base === undefined) { + return Promise.reject({ + errors: [{ msg: `No component has been found for id ${key}` }], + }); + } + if (strategy === 'all' || strategy === '') { + components = listAllComponent(base); + } else if (strategy === 'children') { + components = listChildComponent(base); + } else if (strategy === 'leaves') { + components = listLeavesComponent(base); + } + + const componentsMeasures: ComponentMeasure[] = components.map((c) => { + return { + measures: this.findComponentTree(c.key, base)?.measures, + ...pick(c, ['analysisDate', 'key', 'name', 'qualifier']), + }; + }); + + return this.reply({ + baseComponent: base.component, + components: componentsMeasures.slice(ps * (p - 1), ps * (p - 1) + ps), + metrics: [], + paging: { + pageSize: ps, + pageIndex: p, + total: componentsMeasures.length, + }, + }); + }; + + handleGetTree = ({ + component: key, + q = '', + qualifiers, + }: GetTreeParams & { qualifiers?: string }): Promise<{ + baseComponent: TreeComponent; + components: TreeComponent[]; + paging: Paging; + }> => { + const base = this.findComponentTree(key); + if (base === undefined) { + return Promise.reject({ + errors: [{ msg: `No component has been found for key ${key}` }], + }); + } + const components: TreeComponent[] = listAllComponent(base) + .filter(({ name, key }) => name.includes(q) || key.includes(q)) + .filter(({ qualifier }) => (qualifiers?.length ? qualifiers.includes(qualifier) : true)) + .map((c) => ({ ...c, visibility: Visibility.Public })); + + return this.reply({ + baseComponent: { ...base.component, visibility: Visibility.Public }, + components, + paging: { + pageIndex: 1, + pageSize: 100, + total: components.length, + }, + }); + }; + + handleGetComponentData = (data: { component: string } & BranchParameters) => { + if (this.failLoadingComponentStatus !== undefined) { + return Promise.reject({ status: this.failLoadingComponentStatus }); + } + const tree = this.findComponentTree(data.component); + if (tree) { + const { component, ancestors } = tree; + return this.reply({ component, ancestors }); + } + throw new Error(`Couldn't find component with key ${data.component}`); + }; + + handleGetComponentForSourceViewer = ({ component }: { component: string } & BranchParameters) => { + const sourceFile = this.findSourceFile(component); + return this.reply(sourceFile.component); + }; + + handleGetDuplications = ({ + key, + }: { key: string } & BranchParameters): Promise<{ + duplications: Duplication[]; + files: Dict<DuplicatedFile>; + }> => { + const { duplication } = this.findSourceFile(key); + if (duplication) { + return this.reply(duplication); + } + return this.reply({ duplications: [], files: {} }); + }; + + handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => { + const { lines } = this.findSourceFile(data.key); + const from = data.from || 1; + const to = data.to || lines.length; + return this.reply(lines.slice(from - 1, to)); + }; + + reply<T>(response: T): Promise<T> { + return Promise.resolve(cloneDeep(response)); + } +} diff --git a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts index 177f06606da..b61ba990b00 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -17,13 +17,9 @@ * 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, range, times } from 'lodash'; +import { cloneDeep, keyBy, times } from 'lodash'; import { RuleDescriptionSections } from '../../apps/coding-rules/rule'; -import { - mockSnippetsByComponent, - mockSourceLine, - mockSourceViewerFile, -} from '../../helpers/mocks/sources'; +import { mockSnippetsByComponent } from '../../helpers/mocks/sources'; import { RequestData } from '../../helpers/request'; import { getStandards } from '../../helpers/security-standard'; import { @@ -33,7 +29,6 @@ import { mockRawIssue, mockRuleDetails, } from '../../helpers/testMocks'; -import { BranchParameters } from '../../types/branch-like'; import { IssueType, RawFacet, @@ -48,10 +43,8 @@ import { RuleActivation, RuleDetails, SnippetsByComponent, - SourceViewerFile, } from '../../types/types'; import { NoticeType } from '../../types/users'; -import { getComponentForSourceViewer, getSources } from '../components'; import { addIssueComment, bulkChangeIssues, @@ -100,20 +93,15 @@ interface IssueData { export default class IssuesServiceMock { isAdmin = false; standards?: Standards; - sourceViewerFiles: SourceViewerFile[]; + defaultList: IssueData[]; list: IssueData[]; constructor() { - // Comment should have their own store as we can test better CRUD operation - this.sourceViewerFiles = [ - mockSourceViewerFile('file.foo', 'project'), - mockSourceViewerFile('file.bar', 'project'), - ]; - this.list = [ + this.defaultList = [ { issue: mockRawIssue(false, { key: 'issue11', - component: 'project:file.foo', + component: 'foo:test1.js', message: 'FlowIssue', rule: 'simpleRuleId', textRange: { @@ -128,7 +116,7 @@ export default class IssuesServiceMock { description: 'Backtracking 1', locations: [ { - component: 'project:file.foo', + component: 'foo:test1.js', msg: 'Data location 1', textRange: { startLine: 20, @@ -138,7 +126,7 @@ export default class IssuesServiceMock { }, }, { - component: 'project:file.foo', + component: 'foo:test1.js', msg: 'Data location 2', textRange: { startLine: 21, @@ -153,7 +141,7 @@ export default class IssuesServiceMock { type: FlowType.EXECUTION, locations: [ { - component: 'project:file.bar', + component: 'foo:test2.js', msg: 'Execution location 1', textRange: { startLine: 20, @@ -163,7 +151,7 @@ export default class IssuesServiceMock { }, }, { - component: 'project:file.bar', + component: 'foo:test2.js', msg: 'Execution location 2', textRange: { startLine: 22, @@ -173,7 +161,7 @@ export default class IssuesServiceMock { }, }, { - component: 'project:file.bar', + component: 'foo:test2.js', msg: 'Execution location 3', textRange: { startLine: 5, @@ -189,13 +177,13 @@ export default class IssuesServiceMock { snippets: keyBy( [ mockSnippetsByComponent( - 'file.foo', - 'project', + 'test1.js', + 'foo', times(40, (i) => i + 1) ), mockSnippetsByComponent( - 'file.bar', - 'project', + 'test2.js', + 'foo', times(40, (i) => i + 1) ), ], @@ -205,7 +193,7 @@ export default class IssuesServiceMock { { issue: mockRawIssue(false, { key: 'issue0', - component: 'project:file.foo', + component: 'foo:test1.js', message: 'Issue on file', rule: 'simpleRuleId', textRange: undefined, @@ -216,7 +204,7 @@ export default class IssuesServiceMock { { issue: mockRawIssue(false, { key: 'issue1', - component: 'project:file.foo', + component: 'foo:test1.js', message: 'Fix this', rule: 'simpleRuleId', textRange: { @@ -229,7 +217,7 @@ export default class IssuesServiceMock { { locations: [ { - component: 'project:file.foo', + component: 'foo:test1.js', msg: 'location 1', textRange: { startLine: 1, @@ -243,7 +231,7 @@ export default class IssuesServiceMock { { locations: [ { - component: 'project:file.bar', + component: 'foo:test2.js', msg: 'location 2', textRange: { startLine: 20, @@ -259,13 +247,13 @@ export default class IssuesServiceMock { snippets: keyBy( [ mockSnippetsByComponent( - 'file.foo', - 'project', + 'test1.js', + 'foo', times(40, (i) => i + 1) ), mockSnippetsByComponent( - 'file.bar', - 'project', + 'test2.js', + 'foo', times(40, (i) => i + 1) ), ], @@ -277,7 +265,7 @@ export default class IssuesServiceMock { actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'], transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], key: 'issue2', - component: 'project:file.bar', + component: 'foo:test2.js', message: 'Fix that', rule: 'advancedRuleId', textRange: { @@ -291,8 +279,8 @@ export default class IssuesServiceMock { snippets: keyBy( [ mockSnippetsByComponent( - 'file.bar', - 'project', + 'test2.js', + 'foo', times(40, (i) => i + 20) ), ], @@ -302,7 +290,7 @@ export default class IssuesServiceMock { { issue: mockRawIssue(false, { key: 'issue3', - component: 'project:file.bar', + component: 'foo:test2.js', message: 'Second issue', rule: 'other', textRange: { @@ -315,8 +303,8 @@ export default class IssuesServiceMock { snippets: keyBy( [ mockSnippetsByComponent( - 'file.bar', - 'project', + 'test2.js', + 'foo', times(40, (i) => i + 20) ), ], @@ -328,7 +316,7 @@ export default class IssuesServiceMock { actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'], transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'], key: 'issue4', - component: 'project:file.bar', + component: 'foo:test2.js', message: 'Issue with tags', rule: 'external_eslint_repo:no-div-regex', textRange: { @@ -344,8 +332,8 @@ export default class IssuesServiceMock { snippets: keyBy( [ mockSnippetsByComponent( - 'file.bar', - 'project', + 'test2.js', + 'foo', times(40, (i) => i + 20) ), ], @@ -353,13 +341,12 @@ export default class IssuesServiceMock { ), }, ]; + + this.list = cloneDeep(this.defaultList); + (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 - ); (bulkChangeIssues as jest.Mock).mockImplementation(this.handleBulkChangeIssues); (getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser); (dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification); @@ -375,6 +362,10 @@ export default class IssuesServiceMock { (searchIssueTags as jest.Mock).mockImplementation(this.handleSearchIssueTags); } + reset = () => { + this.list = cloneDeep(this.defaultList); + }; + async getStandards(): Promise<Standards> { if (this.standards) { return this.standards; @@ -404,21 +395,6 @@ export default class IssuesServiceMock { return this.reply({}); }; - 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) { @@ -622,10 +598,14 @@ export default class IssuesServiceMock { }; getActionsResponse = (overrides: Partial<RawIssue>, issueKey: string) => { - const issueDataSelected = this.list.find((l) => l.issue.key === issueKey)!; + const issueDataSelected = this.list.find((l) => l.issue.key === issueKey); + + if (!issueDataSelected) { + throw new Error(`Coulnd't find issue for key ${issueKey}`); + } issueDataSelected.issue = { - ...issueDataSelected?.issue, + ...issueDataSelected.issue, ...overrides, }; diff --git a/server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts deleted file mode 100644 index 90ff8c7098d..00000000000 --- a/server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts +++ /dev/null @@ -1,286 +0,0 @@ -/* - * 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. - */ -/* eslint-disable no-template-curly-in-string */ - -import { cloneDeep, pick, times } from 'lodash'; -import { - getComponentData, - getComponentForSourceViewer, - getDuplications, - getSources, -} from '../../api/components'; -import { mockSourceLine } from '../../helpers/mocks/sources'; -import { HttpStatus } from '../../helpers/request'; -import { BranchParameters } from '../../types/branch-like'; -import { Dict } from '../../types/types'; -import { setIssueSeverity } from '../issues'; - -function mockSourceFileView(name: string, project = 'project') { - return { - component: { - key: `${project}:${name}`, - name, - qualifier: 'FIL', - path: name, - language: 'js', - analysisDate: '2019-08-08T12:15:12+0200', - leakPeriodDate: '2018-08-07T11:22:22+0200', - version: '1.2-SNAPSHOT', - needIssueSync: false, - }, - sourceFileView: { - key: `${project}:${name}`, - uuid: `AWMgNpveti8CNlpVyHAm${project}${name}`, - path: name, - name, - longName: name, - q: 'FIL', - project, - projectName: 'Test project', - fav: false, - canMarkAsFavorite: true, - measures: { lines: '0', issues: '0' }, - }, - }; -} - -const ANCESTORS = [ - { - key: 'project', - name: 'Test project', - description: '', - qualifier: 'TRK', - analysisDate: '2019-08-08T12:15:12+0200', - tags: [], - visibility: 'public', - leakPeriodDate: '2018-08-07T11:22:22+0200', - version: '1.2-SNAPSHOT', - needIssueSync: false, - }, -]; -const FILES: Dict<any> = { - 'project:test3.js': { - ...mockSourceFileView('test3.js'), - sources: [], - ancestors: ANCESTORS, - }, - 'project:test2.js': { - ...mockSourceFileView('test2.js'), - sources: times(200, (n) => - mockSourceLine({ - line: n, - code: `\u003cspan class\u003d"cd"\u003eLine ${n}\u003c/span\u003e`, - }) - ), - ancestors: ANCESTORS, - }, - 'foo:index.tsx': { - ...mockSourceFileView('index.tsx', 'foo'), - sources: times(200, (n) => - mockSourceLine({ - line: n, - code: 'function Test() {}', - }) - ), - ancestors: ANCESTORS, - }, - 'project:testSymb.tsx': { - ...mockSourceFileView('testSymb.tsx'), - sources: times(20, (n) => - mockSourceLine({ - line: n, - code: ' <span class="sym-35 sym">symbole</span>', - }) - ), - ancestors: ANCESTORS, - }, - 'project:test.js': { - ...mockSourceFileView('test.js'), - sources: [ - { - 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, - }, - ], - ancestors: ANCESTORS, - duplications: [ - { - blocks: [ - { from: 7, size: 1, _ref: '1' }, - { from: 1, size: 1, _ref: '2' }, - ], - }, - ], - files: { - '1': { - key: 'project:test.js', - name: 'test.js', - uuid: 'AX8NSmj8EGYw5-dyy63J', - project: 'project', - projectUuid: 'AX7juKJqVeQLJMPyb_b-', - projectName: 'project', - }, - '2': { - key: 'project:test2.js', - name: 'test2.js', - uuid: 'BX8NSmj8EGYw5-dyy63J', - project: 'project', - projectUuid: 'AX7juKJqVeQLJMPyb_b-', - projectName: 'project', - }, - }, - }, -}; - -export class SourceViewerServiceMock { - faileLoadingComponentStatus: HttpStatus | undefined = undefined; - - constructor() { - (getComponentData as jest.Mock).mockImplementation(this.handleGetComponentData); - (getComponentForSourceViewer as jest.Mock).mockImplementation( - this.handleGetComponentForSourceViewer - ); - (getDuplications as jest.Mock).mockImplementation(this.handleGetDuplications); - (getSources as jest.Mock).mockImplementation(this.handleGetSources); - (setIssueSeverity as jest.Mock).mockImplementation(this.handleSetIssueSeverity); - } - - reset() { - this.faileLoadingComponentStatus = undefined; - } - - setFailLoadingComponentStatus(status: HttpStatus.Forbidden | HttpStatus.NotFound) { - this.faileLoadingComponentStatus = status; - } - - getHugeFile(): string { - return 'project:test2.js'; - } - - getEmptyFile(): string { - return 'project:test3.js'; - } - - getFileWithSource(): string { - return 'project:test.js'; - } - - handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => { - const { sources } = FILES[data.key]; - const from = data.from || 1; - const to = data.to || sources.length; - return this.reply(sources.slice(from - 1, to)); - }; - - handleGetDuplications = (data: { key: string } & BranchParameters) => { - return this.reply(pick(FILES[data.key], ['duplications', 'files'])); - }; - - handleGetComponentForSourceViewer = (data: { component: string } & BranchParameters) => { - return this.reply(FILES[data.component]['sourceFileView']); - }; - - handleGetComponentData = (data: { component: string } & BranchParameters) => { - if (this.faileLoadingComponentStatus !== undefined) { - return Promise.reject({ status: this.faileLoadingComponentStatus }); - } - return this.reply(pick(FILES[data.component], ['component', 'ancestor'])); - }; - - handleSetIssueSeverity = () => { - return this.reply({}); - }; - - reply<T>(response: T): Promise<T> { - return Promise.resolve(cloneDeep(response)); - } -} |