From 2bed98c4ceafc4eb913f16c11a9e72508096636c Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Fri, 2 Jun 2023 08:59:38 +0200 Subject: [PATCH] SONAR-18425 Migrate measures page tests to RTL --- .../src/components/BubbleChart.tsx | 19 +- .../sonar-web/src/main/js/api/components.ts | 36 +- .../js/api/mocks/ComponentsServiceMock.ts | 116 ++- .../main/js/api/mocks/IssuesServiceMock.ts | 13 +- .../main/js/api/mocks/MeasuresServiceMock.ts | 119 +++ .../src/main/js/api/mocks/data/components.ts | 65 +- .../src/main/js/api/mocks/data/ids.ts | 9 +- .../src/main/js/api/mocks/data/issues.ts | 46 +- .../src/main/js/api/mocks/data/measures.ts | 232 +++++ .../src/main/js/api/mocks/data/utils.ts | 53 + .../main/js/apps/code/__tests__/Code-it.ts | 106 +- .../__tests__/SourceViewerWrapper-test.tsx | 7 - .../__tests__/ComponentMeasures-it.tsx | 485 +++++++++ .../__tests__/MeasuresApp-it.tsx | 34 - .../components/ComponentMeasuresApp.tsx | 4 +- .../components/__tests__/Breadcrumb-test.tsx | 56 -- .../components/__tests__/Breadcrumbs-test.tsx | 92 -- .../__tests__/ComponentMeasuresApp-test.tsx | 165 ---- .../__tests__/MeasureContent-test.tsx | 209 ---- .../__tests__/MeasureHeader-test.tsx | 104 -- .../__tests__/MeasureOverview-test.tsx | 133 --- .../__tests__/MeasureViewSelect-test.tsx | 50 - .../__snapshots__/Breadcrumb-test.tsx.snap | 27 - .../__snapshots__/Breadcrumbs-test.tsx.snap | 34 - .../ComponentMeasuresApp-test.tsx.snap | 227 ----- .../MeasureContent-test.tsx.snap | 927 ------------------ .../__snapshots__/MeasureHeader-test.tsx.snap | 244 ----- .../MeasureOverview-test.tsx.snap | 414 -------- .../MeasureViewSelect-test.tsx.snap | 102 -- .../drilldown/BubbleChart.tsx | 1 + .../drilldown/__tests__/BubbleChart-test.tsx | 106 -- .../__tests__/ComponentCell-test.tsx | 178 ---- .../__tests__/ComponentList-test.tsx | 80 -- .../drilldown/__tests__/FilesView-test.tsx | 124 --- .../drilldown/__tests__/MeasureCell-test.tsx | 47 - .../__snapshots__/ComponentCell-test.tsx.snap | 264 ----- .../__snapshots__/ComponentList-test.tsx.snap | 132 --- .../__snapshots__/FilesView-test.tsx.snap | 111 --- .../sidebar/__tests__/DomainFacet-test.tsx | 114 --- .../__tests__/FacetMeasureValue-test.tsx | 59 -- .../sidebar/__tests__/Sidebar-test.tsx | 84 -- .../__snapshots__/DomainFacet-test.tsx.snap | 447 --------- .../FacetMeasureValue-test.tsx.snap | 40 - .../__snapshots__/Sidebar-test.tsx.snap | 93 -- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 9 +- .../src/main/js/apps/issues/test-utils.tsx | 5 - .../projectKey/__tests__/ProjectKeyApp-it.tsx | 2 - .../js/apps/users/__tests__/UsersApp-it.tsx | 1 - .../__tests__/SourceViewer-it.tsx | 7 - .../components/issue/__tests__/Issue-it.tsx | 4 - .../src/main/js/helpers/mocks/metrics.ts | 4 +- 51 files changed, 1162 insertions(+), 4878 deletions(-) create mode 100644 server/sonar-web/src/main/js/api/mocks/MeasuresServiceMock.ts create mode 100644 server/sonar-web/src/main/js/api/mocks/data/measures.ts create mode 100644 server/sonar-web/src/main/js/api/mocks/data/utils.ts create mode 100644 server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/ComponentMeasuresApp-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/ComponentMeasuresApp-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/BubbleChart-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentCell-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/ComponentList-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/FilesView-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/MeasureCell-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentCell-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/ComponentList-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/drilldown/__tests__/__snapshots__/FilesView-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/DomainFacet-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/FacetMeasureValue-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/Sidebar-test.tsx delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/FacetMeasureValue-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap diff --git a/server/sonar-web/design-system/src/components/BubbleChart.tsx b/server/sonar-web/design-system/src/components/BubbleChart.tsx index e35aba51308..7a2531eb2ce 100644 --- a/server/sonar-web/design-system/src/components/BubbleChart.tsx +++ b/server/sonar-web/design-system/src/components/BubbleChart.tsx @@ -47,6 +47,7 @@ interface BubbleItem { } export interface BubbleChartProps { + 'data-testid'?: string; displayXGrid?: boolean; displayXTicks?: boolean; displayYGrid?: boolean; @@ -327,7 +328,13 @@ export function BubbleChart(props: BubbleChartProps) { const yTicks = getTicks(yScale, props.formatYTick); return ( - + (props: BubbleProps) { diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index ddecc845d70..60d38ed488c 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -64,6 +64,18 @@ export interface SearchProjectsParameters extends BaseSearchProjectsParameters { ps?: number; } +export interface ComponentRaw { + key: string; + name: string; + isFavorite?: boolean; + analysisDate?: string; + qualifier: ComponentQualifier; + tags: string[]; + visibility: Visibility; + leakPeriodDate?: string; + needIssueSync?: boolean; +} + export function getComponents(parameters: SearchProjectsParameters): Promise<{ components: Project[]; paging: Paging; @@ -168,7 +180,10 @@ export function getDirectories(data: GetTreeParams) { return getTree({ ...data, qualifiers: 'DIR' }); } -export function getComponentData(data: { component: string } & BranchParameters): Promise { +export function getComponentData(data: { component: string } & BranchParameters): Promise<{ + ancestors: Array>; + component: Omit; +}> { return getJSON('/api/components/show', data); } @@ -189,7 +204,9 @@ export function getParents(component: string): Promise { return getComponentShow({ component }).then((r) => r.ancestors); } -export function getBreadcrumbs(data: { component: string } & BranchParameters): Promise { +export function getBreadcrumbs( + data: { component: string } & BranchParameters +): Promise>> { return getComponentShow(data).then((r) => { const reversedAncestors = [...r.ancestors].reverse(); return [...reversedAncestors, r.component]; @@ -203,26 +220,13 @@ export function getMyProjects(data: { return getJSON('/api/projects/search_my_projects', data); } -export interface Component { - id: string; - key: string; - name: string; - isFavorite?: boolean; - analysisDate?: string; - qualifier: ComponentQualifier; - tags: string[]; - visibility: Visibility; - leakPeriodDate?: string; - needIssueSync?: boolean; -} - export interface Facet { property: string; values: Array<{ val: string; count: number }>; } export function searchProjects(data: RequestData): Promise<{ - components: Component[]; + components: ComponentRaw[]; facets: Facet[]; paging: Paging; }> { diff --git a/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts index 15573b1dbcf..1a9eb241a1f 100644 --- a/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts @@ -17,8 +17,11 @@ * 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 } from 'lodash'; +import { cloneDeep, pick } from 'lodash'; +import { DEFAULT_METRICS } from '../../helpers/mocks/metrics'; import { HttpStatus, RequestData } from '../../helpers/request'; +import { mockMetric } from '../../helpers/testMocks'; +import { isDefined } from '../../helpers/types'; import { BranchParameters } from '../../types/branch-like'; import { TreeComponent, Visibility } from '../../types/component'; import { @@ -31,11 +34,14 @@ import { Paging, } from '../../types/types'; import { + ComponentRaw, GetTreeParams, changeKey, + getBreadcrumbs, getChildren, getComponentData, getComponentForSourceViewer, + getComponentLeaves, getComponentTree, getDuplications, getSources, @@ -47,29 +53,11 @@ import { mockFullComponentTree, mockFullSourceViewerFileList, } from './data/components'; +import { mockIssuesList } from './data/issues'; +import { MeasureRecords, mockFullMeasureData } from './data/measures'; +import { listAllComponent, listChildComponent, listLeavesComponent } from './data/utils'; -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); -} +jest.mock('../components'); export default class ComponentsServiceMock { failLoadingComponentStatus: HttpStatus | undefined = undefined; @@ -77,13 +65,23 @@ export default class ComponentsServiceMock { components: ComponentTree[]; defaultSourceFiles: SourceFile[]; sourceFiles: SourceFile[]; + defaultMeasures: MeasureRecords; + measures: MeasureRecords; - constructor(components?: ComponentTree[], sourceFiles?: SourceFile[]) { + constructor(components?: ComponentTree[], sourceFiles?: SourceFile[], measures?: MeasureRecords) { this.defaultComponents = components || [mockFullComponentTree()]; this.defaultSourceFiles = sourceFiles || mockFullSourceViewerFileList(); + const issueList = mockIssuesList(); + this.defaultMeasures = + measures || + this.defaultComponents.reduce( + (acc, tree) => ({ ...acc, ...mockFullMeasureData(tree, issueList) }), + {} + ); this.components = cloneDeep(this.defaultComponents); this.sourceFiles = cloneDeep(this.defaultSourceFiles); + this.measures = cloneDeep(this.defaultMeasures); jest.mocked(getComponentTree).mockImplementation(this.handleGetComponentTree); jest.mocked(getChildren).mockImplementation(this.handleGetChildren); @@ -95,27 +93,32 @@ export default class ComponentsServiceMock { jest.mocked(getDuplications).mockImplementation(this.handleGetDuplications); jest.mocked(getSources).mockImplementation(this.handleGetSources); jest.mocked(changeKey).mockImplementation(this.handleChangeKey); + jest.mocked(getComponentLeaves).mockImplementation(this.handleGetComponentLeaves); + jest.mocked(getBreadcrumbs).mockImplementation(this.handleGetBreadcrumbs); } - findComponentTree = (key: string, from?: ComponentTree): ComponentTree | undefined => { - const recurse = (node: ComponentTree): ComponentTree | undefined => { + findComponentTree = (key: string, from?: ComponentTree) => { + let tree: ComponentTree | undefined; + const recurse = (node: ComponentTree): boolean => { if (node.component.key === key) { - return node; + tree = node; + return true; } - return node.children.find((child) => recurse(child)); + return node.children.some((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; - } + if (from !== undefined) { + recurse(from); + return tree; + } + + for (let i = 0, len = this.components.length; i < len; i++) { + if (recurse(this.components[i])) { + return tree; } - throw new Error(`Couldn't find component tree for key ${key}`); } - return recurse(from); + throw new Error(`Couldn't find component tree for key ${key}`); }; findSourceFile = (key: string): SourceFile => { @@ -137,6 +140,10 @@ export default class ComponentsServiceMock { this.components.push(componentTree); }; + registerComponentMeasures = (measures: MeasureRecords) => { + this.measures = measures; + }; + setFailLoadingComponentStatus = (status: HttpStatus.Forbidden | HttpStatus.NotFound) => { this.failLoadingComponentStatus = status; }; @@ -196,6 +203,7 @@ export default class ComponentsServiceMock { reset = () => { this.components = cloneDeep(this.defaultComponents); this.sourceFiles = cloneDeep(this.defaultSourceFiles); + this.measures = cloneDeep(this.defaultMeasures); }; handleGetChildren = ( @@ -214,7 +222,7 @@ export default class ComponentsServiceMock { handleGetComponentTree = ( strategy: string, key: string, - _metrics: string[] = [], + metricKeys: string[] = [], { p = 1, ps = 100 }: RequestData = {} ): Promise<{ baseComponent: ComponentMeasure; @@ -239,7 +247,9 @@ export default class ComponentsServiceMock { const componentsMeasures: ComponentMeasure[] = components.map((c) => { return { - measures: this.findComponentTree(c.key, base)?.measures, + measures: metricKeys + .map((metric) => this.measures[c.key] && this.measures[c.key][metric]) + .filter(isDefined), ...pick(c, ['analysisDate', 'key', 'name', 'qualifier']), }; }); @@ -247,7 +257,7 @@ export default class ComponentsServiceMock { return this.reply({ baseComponent: base.component, components: componentsMeasures.slice(ps * (p - 1), ps * (p - 1) + ps), - metrics: [], + metrics: metricKeys.map((metric) => DEFAULT_METRICS[metric] ?? mockMetric({ key: metric })), paging: { pageSize: ps, pageIndex: p, @@ -294,7 +304,10 @@ export default class ComponentsServiceMock { const tree = this.findComponentTree(data.component); if (tree) { const { component, ancestors } = tree; - return this.reply({ component, ancestors }); + return this.reply({ component, ancestors } as { + component: ComponentRaw; + ancestors: ComponentRaw[]; + }); } throw new Error(`Couldn't find component with key ${data.component}`); }; @@ -333,6 +346,29 @@ export default class ComponentsServiceMock { return Promise.reject({ status: 404, message: 'Component not found' }); }; + handleGetComponentLeaves = ( + component: string, + metrics: string[] = [], + data: RequestData = {} + ): Promise<{ + baseComponent: ComponentMeasure; + components: ComponentMeasure[]; + metrics: Metric[]; + paging: Paging; + }> => { + return this.handleGetComponentTree('leaves', component, metrics, data); + }; + + handleGetBreadcrumbs = ({ component: key }: { component: string } & BranchParameters) => { + const base = this.findComponentTree(key); + if (base === undefined) { + return Promise.reject({ + errors: [{ msg: `No component has been found for id ${key}` }], + }); + } + return this.reply([...(base.ancestors as ComponentRaw[]), base.component as ComponentRaw]); + }; + reply(response: T): Promise { 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 a39e74e6c6a..2752252946a 100644 --- a/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts @@ -56,9 +56,15 @@ import { } from '../issues'; import { getRuleDetails, searchRules } from '../rules'; import { dismissNotice, getCurrentUser, searchUsers } from '../users'; -import { mockIssuesList } from './data/issues'; +import { IssueData, mockIssuesList } from './data/issues'; import { mockRuleList } from './data/rules'; +jest.mock('../../api/issues'); +// The following 2 mocks are needed, because IssuesServiceMock mocks more than it should. +// This should be removed once IssuesServiceMock is cleaned up. +jest.mock('../../api/rules'); +jest.mock('../../api/users'); + function mockReferenceComponent(override?: Partial) { return { key: 'component1', @@ -82,11 +88,6 @@ function generateReferenceComponentsForIssues(issueData: IssueData[]) { .map((key) => mockReferenceComponent({ key, enabled: true })); } -interface IssueData { - issue: RawIssue; - snippets: Dict; -} - export default class IssuesServiceMock { isAdmin = false; currentUser: LoggedInUser; diff --git a/server/sonar-web/src/main/js/api/mocks/MeasuresServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/MeasuresServiceMock.ts new file mode 100644 index 00000000000..2e5ed685cc1 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/MeasuresServiceMock.ts @@ -0,0 +1,119 @@ +/* + * 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 { cloneDeep } from 'lodash'; +import { mockPeriod } from '../../helpers/testMocks'; +import { BranchParameters } from '../../types/branch-like'; +import { Period } from '../../types/types'; +import { getMeasures, getMeasuresWithPeriod } from '../measures'; +import { ComponentTree, mockFullComponentTree } from './data/components'; +import { mockIssuesList } from './data/issues'; +import { MeasureRecords, mockFullMeasureData } from './data/measures'; + +jest.mock('../measures'); + +const defaultComponents = mockFullComponentTree(); +const defaultMeasures = mockFullMeasureData(defaultComponents, mockIssuesList()); +const defaultPeriod = mockPeriod(); + +export class MeasuresServiceMock { + #components: ComponentTree; + #measures: MeasureRecords; + #period: Period; + reset: () => void; + + constructor(components?: ComponentTree, measures?: MeasureRecords, period?: Period) { + this.#components = components ?? cloneDeep(defaultComponents); + this.#measures = measures ?? cloneDeep(defaultMeasures); + this.#period = period ?? cloneDeep(defaultPeriod); + + this.reset = () => { + this.#components = components ?? cloneDeep(defaultComponents); + this.#measures = measures ?? cloneDeep(defaultMeasures); + this.#period = period ?? cloneDeep(defaultPeriod); + }; + + jest.mocked(getMeasures).mockImplementation(this.handleGetMeasures); + jest.mocked(getMeasuresWithPeriod).mockImplementation(this.handleGetMeasuresWithPeriod); + } + + registerComponentMeasures = (measures: MeasureRecords) => { + this.#measures = measures; + }; + + getComponentMeasures = () => { + return this.#measures; + }; + + findComponentTree = (key: string, from?: ComponentTree): ComponentTree => { + const recurse = (node: ComponentTree): ComponentTree | undefined => { + if (node.component.key === key) { + return node; + } + return node.children.find((child) => recurse(child)); + }; + + const tree = recurse(from ?? this.#components); + if (!tree) { + throw new Error(`Couldn't find component tree for key ${key}`); + } + + return tree; + }; + + filterMeasures = (componentKey: string, metricKeys: string[]) => { + return this.#measures[componentKey] + ? Object.values(this.#measures[componentKey]).filter(({ metric }) => + metricKeys.includes(metric) + ) + : []; + }; + + handleGetMeasures = ({ + component, + metricKeys, + }: { component: string; metricKeys: string } & BranchParameters) => { + const entry = this.findComponentTree(component); + const measures = this.filterMeasures(entry.component.key, metricKeys.split(',')); + + return this.reply(measures); + }; + + handleGetMeasuresWithPeriod = ( + component: string, + metrics: string[], + _branchParameters?: BranchParameters + ) => { + const entry = this.findComponentTree(component); + const measures = this.filterMeasures(entry.component.key, metrics); + + return this.reply({ + component: { + ...entry.component, + measures, + }, + period: this.#period, + }); + }; + + reply(response: T): Promise { + return Promise.resolve(cloneDeep(response)); + } +} 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 index 75497170265..89651428ef5 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/components.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/components.ts @@ -19,7 +19,6 @@ */ import { times } from 'lodash'; -import { isDiffMetric } from '../../../helpers/measures'; import { mockComponent } from '../../../helpers/mocks/component'; import { mockDuplicatedFile, @@ -28,15 +27,12 @@ import { 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'; @@ -48,6 +44,7 @@ import { FILE5_KEY, FILE6_KEY, FILE7_KEY, + FILE8_KEY, FOLDER1_KEY, PARENT_COMPONENT_KEY, } from './ids'; @@ -55,7 +52,6 @@ import { export interface ComponentTree { component: Component; ancestors: Component[]; - measures?: Measure[]; children: ComponentTree[]; } @@ -91,31 +87,13 @@ export function mockFullComponentTree( }, ], }); - 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({ @@ -133,8 +111,24 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent, folderComponent], - - measures, + children: [], + }, + { + component: mockComponent({ + key: `${baseComponent.key}:${FOLDER1_KEY}/${FILE8_KEY}`, + name: FILE8_KEY, + path: `${FOLDER1_KEY}/${FILE8_KEY}`, + qualifier: ComponentQualifier.File, + breadcrumbs: [ + ...folderComponent.breadcrumbs, + { + key: `${baseComponent.key}:${FOLDER1_KEY}/${FILE8_KEY}`, + name: FILE8_KEY, + qualifier: ComponentQualifier.File, + }, + ], + }), + ancestors: [baseComponent, folderComponent], children: [], }, ], @@ -155,8 +149,6 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent], - - measures, children: [], }, { @@ -175,8 +167,6 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent], - - measures, children: [], }, { @@ -195,8 +185,6 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent], - - measures, children: [], }, { @@ -215,8 +203,6 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent], - - measures, children: [], }, { @@ -235,8 +221,6 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent], - - measures, children: [], }, { @@ -255,8 +239,6 @@ export function mockFullComponentTree( ], }), ancestors: [baseComponent], - - measures, children: [], }, ], @@ -283,6 +265,15 @@ export function mockFullSourceViewerFileList(baseComponentKey = PARENT_COMPONENT }) ), }, + { + component: mockSourceViewerFile(`${FOLDER1_KEY}/${FILE8_KEY}`, baseComponentKey), + lines: times(50, (n) => + mockSourceLine({ + line: n, + code: 'function Test() {}', + }) + ), + }, { component: mockSourceViewerFile(FILE2_KEY, baseComponentKey), lines: [ 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 index 38b83514e7e..2db8be77c11 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/ids.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/ids.ts @@ -30,6 +30,7 @@ export const FILE6_KEY = 'huge.js'; export const FOLDER1_KEY = 'folderA'; //// Inside folderA. export const FILE7_KEY = 'out.tsx'; +export const FILE8_KEY = 'in.tsx'; // Rules. export const SIMPLE_RULE = 'simpleRuleId'; @@ -46,6 +47,8 @@ export const ISSUE_4 = 'issue4'; export const ISSUE_11 = 'issue11'; export const ISSUE_101 = 'issue101'; export const ISSUE_1101 = 'issue1101'; +export const ISSUE_1102 = 'issue1102'; +export const ISSUE_1103 = 'issue1103'; // Issue to rule map. export const ISSUE_TO_RULE = { @@ -57,6 +60,8 @@ export const ISSUE_TO_RULE = { [ISSUE_11]: SIMPLE_RULE, [ISSUE_101]: SIMPLE_RULE, [ISSUE_1101]: SIMPLE_RULE, + [ISSUE_1102]: SIMPLE_RULE, + [ISSUE_1103]: SIMPLE_RULE, }; // Issue to files map. @@ -68,5 +73,7 @@ export const ISSUE_TO_FILES = { [ISSUE_4]: [FILE3_KEY], [ISSUE_11]: [FILE2_KEY, FILE3_KEY], [ISSUE_101]: [FILE2_KEY, FILE3_KEY], - [ISSUE_1101]: [FILE7_KEY], + [ISSUE_1101]: [`${FOLDER1_KEY}/${FILE7_KEY}`], + [ISSUE_1102]: [`${FOLDER1_KEY}/${FILE8_KEY}`], + [ISSUE_1103]: [`${FOLDER1_KEY}/${FILE8_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 index 86fe6aa2755..db54959bdab 100644 --- a/server/sonar-web/src/main/js/api/mocks/data/issues.ts +++ b/server/sonar-web/src/main/js/api/mocks/data/issues.ts @@ -28,14 +28,17 @@ import { IssueSeverity, IssueStatus, IssueType, + RawIssue, } from '../../../types/issues'; -import { FlowType } from '../../../types/types'; +import { Dict, FlowType, SnippetsByComponent } from '../../../types/types'; import { ISSUE_0, ISSUE_1, ISSUE_101, ISSUE_11, ISSUE_1101, + ISSUE_1102, + ISSUE_1103, ISSUE_2, ISSUE_3, ISSUE_4, @@ -44,7 +47,12 @@ import { PARENT_COMPONENT_KEY, } from './ids'; -export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY) { +export interface IssueData { + issue: RawIssue; + snippets: Dict; +} + +export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY): IssueData[] { return [ { issue: mockRawIssue(false, { @@ -378,5 +386,39 @@ export function mockIssuesList(baseComponentKey = PARENT_COMPONENT_KEY) { }), snippets: {}, }, + { + issue: mockRawIssue(false, { + key: ISSUE_1102, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1102][0]}`, + message: 'Issue inside folderA', + creationDate: '2022-01-15T09:36:01+0100', + type: IssueType.CodeSmell, + rule: ISSUE_TO_RULE[ISSUE_1102], + textRange: { + startLine: 10, + endLine: 10, + startOffset: 0, + endOffset: 2, + }, + }), + snippets: {}, + }, + { + issue: mockRawIssue(false, { + key: ISSUE_1103, + component: `${baseComponentKey}:${ISSUE_TO_FILES[ISSUE_1103][0]}`, + creationDate: '2022-01-15T09:36:01+0100', + message: 'Issue inside folderA', + type: IssueType.CodeSmell, + rule: ISSUE_TO_RULE[ISSUE_1103], + textRange: { + startLine: 10, + endLine: 10, + startOffset: 0, + endOffset: 2, + }, + }), + snippets: {}, + }, ]; } diff --git a/server/sonar-web/src/main/js/api/mocks/data/measures.ts b/server/sonar-web/src/main/js/api/mocks/data/measures.ts new file mode 100644 index 00000000000..73e76d3349a --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/data/measures.ts @@ -0,0 +1,232 @@ +/* + * 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 } from 'lodash'; +import { isDiffMetric } from '../../../helpers/measures'; +import { mockMeasure } from '../../../helpers/testMocks'; +import { IssueStatus, IssueType, RawIssue } from '../../../types/issues'; +import { MetricKey } from '../../../types/metrics'; +import { Measure } from '../../../types/types'; +import { ComponentTree } from './components'; +import { IssueData } from './issues'; +import { listAllComponent, listAllComponentTrees } from './utils'; + +export type MeasureRecords = Record>; + +export function mockFullMeasureData(tree: ComponentTree, issueList: IssueData[]) { + const measures: MeasureRecords = {}; + listAllComponentTrees(tree).forEach((tree) => { + measures[tree.component.key] = keyBy( + Object.values(MetricKey).map((metricKey) => mockComponentMeasure(tree, issueList, metricKey)), + 'metric' + ); + }); + return measures; +} + +function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metricKey: MetricKey) { + const componentKeys = listAllComponent(tree).map(({ key }) => key); + + switch (metricKey) { + case MetricKey.ncloc: + return mockMeasure({ + metric: metricKey, + value: '16000', + }); + + case MetricKey.ncloc_language_distribution: + return mockMeasure({ + metric: metricKey, + value: 'java=10000;javascript=5000;css=1000', + }); + } + + const issues = issueList + .map(({ issue }) => issue) + .filter(({ component }) => componentKeys.includes(component)) + .filter(({ status }) => + [IssueStatus.Open, IssueStatus.Reopened, IssueStatus.Confirmed].includes( + status as IssueStatus + ) + ); + + if (isIssueType(metricKey)) { + switch (metricKey) { + case MetricKey.bugs: + return mockMeasure({ + metric: metricKey, + period: undefined, + value: String(issues.filter(({ type }) => type === IssueType.Bug).length), + }); + + case MetricKey.new_bugs: + return mockMeasure({ + metric: metricKey, + period: { + index: 0, + value: String(issues.filter(({ type }) => type === IssueType.Bug).length), + }, + value: undefined, + }); + + case MetricKey.code_smells: + return mockMeasure({ + metric: metricKey, + period: undefined, + value: String(issues.filter(({ type }) => type === IssueType.CodeSmell).length), + }); + + case MetricKey.new_code_smells: + return mockMeasure({ + metric: metricKey, + period: { + index: 0, + value: String(issues.filter(({ type }) => type === IssueType.CodeSmell).length), + }, + value: undefined, + }); + + case MetricKey.vulnerabilities: + return mockMeasure({ + metric: metricKey, + period: undefined, + value: String(issues.filter(({ type }) => type === IssueType.Vulnerability).length), + }); + + case MetricKey.new_vulnerabilities: + return mockMeasure({ + metric: metricKey, + period: { + index: 0, + value: String(issues.filter(({ type }) => type === IssueType.Vulnerability).length), + }, + value: undefined, + }); + + case MetricKey.open_issues: + return mockMeasure({ + metric: metricKey, + value: String(issues.length), + }); + } + } + + if (isIssueRelatedRating(metricKey)) { + switch (metricKey) { + case MetricKey.reliability_rating: + return mockMeasure({ + metric: metricKey, + period: undefined, + ...computeRating(issues, IssueType.Bug), + }); + + case MetricKey.new_reliability_rating: + return mockMeasure({ + metric: metricKey, + period: { + index: 0, + ...computeRating(issues, IssueType.Bug), + }, + value: undefined, + }); + + case MetricKey.sqale_rating: + return mockMeasure({ + metric: metricKey, + period: undefined, + ...computeRating(issues, IssueType.CodeSmell), + }); + + case MetricKey.new_maintainability_rating: + return mockMeasure({ + metric: metricKey, + period: { + index: 0, + ...computeRating(issues, IssueType.CodeSmell), + }, + value: undefined, + }); + + case MetricKey.security_rating: + return mockMeasure({ + metric: metricKey, + period: undefined, + ...computeRating(issues, IssueType.Vulnerability), + }); + + case MetricKey.new_security_rating: + return mockMeasure({ + metric: metricKey, + period: { + index: 0, + ...computeRating(issues, IssueType.Vulnerability), + }, + value: undefined, + }); + } + } + + // Defaults. + if (isDiffMetric(metricKey)) { + return mockMeasure({ + metric: metricKey, + value: undefined, + }); + } + return mockMeasure({ + metric: metricKey, + period: undefined, + }); +} + +function isIssueType(metricKey: MetricKey) { + return [ + MetricKey.bugs, + MetricKey.new_bugs, + MetricKey.code_smells, + MetricKey.new_code_smells, + MetricKey.vulnerabilities, + MetricKey.new_vulnerabilities, + MetricKey.open_issues, + ].includes(metricKey); +} + +function isIssueRelatedRating(metricKey: MetricKey) { + return [ + MetricKey.reliability_rating, + MetricKey.new_reliability_rating, + MetricKey.sqale_rating, + MetricKey.new_maintainability_rating, + MetricKey.security_rating, + MetricKey.new_security_rating, + ].includes(metricKey); +} + +/** + * Ratings are not only based on the number of issues, but also their severity, and sometimes their + * ratio to the LOC. But using the number will suffice as an approximation in our tests. + */ +function computeRating(issues: RawIssue[], type: IssueType) { + const value = Math.max(Math.min(issues.filter((i) => i.type === type).length, 5), 1); + return { + value: `${value}.0`, + bestValue: value === 1, + }; +} diff --git a/server/sonar-web/src/main/js/api/mocks/data/utils.ts b/server/sonar-web/src/main/js/api/mocks/data/utils.ts new file mode 100644 index 00000000000..eaaddae8da0 --- /dev/null +++ b/server/sonar-web/src/main/js/api/mocks/data/utils.ts @@ -0,0 +1,53 @@ +/* + * 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 { flatMap, map } from 'lodash'; +import { Component } from '../../../types/types'; +import { ComponentTree } from './components'; + +export function isLeaf(node: ComponentTree) { + return node.children.length === 0; +} + +export function listChildComponent(node: ComponentTree): Component[] { + return map(node.children, (n) => n.component); +} + +export function listAllComponent(node: ComponentTree): Component[] { + if (isLeaf(node)) { + return [node.component]; + } + + return [node.component, ...flatMap(node.children, listAllComponent)]; +} + +export function listAllComponentTrees(node: ComponentTree): ComponentTree[] { + if (isLeaf(node)) { + return [node]; + } + + return [node, ...flatMap(node.children, listAllComponentTrees)]; +} + +export function listLeavesComponent(node: ComponentTree): Component[] { + if (isLeaf(node)) { + return [node.component]; + } + return flatMap(node.children, listLeavesComponent); +} diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts index 46cda04a7f4..2cd68c56004 100644 --- a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts +++ b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts @@ -20,7 +20,7 @@ import { screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; -import { times } from 'lodash'; +import { keyBy, times } from 'lodash'; import { act } from 'react-dom/test-utils'; import { byRole, byText } from 'testing-library-selector'; import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock'; @@ -34,13 +34,6 @@ import { MetricKey } from '../../../types/metrics'; import { Component } from '../../../types/types'; import routes from '../routes'; -jest.mock('../../../api/components'); -jest.mock('../../../api/issues'); -// The following 2 mocks are needed, because IssuesServiceMock mocks more than it should. -// This should be removed once IssuesServiceMock is cleaned up. -jest.mock('../../../api/rules'); -jest.mock('../../../api/users'); - jest.mock('../../../components/intl/DateFromNow'); jest.mock('../../../components/SourceViewer/helpers/lines', () => { @@ -218,7 +211,6 @@ it('should correctly show measures for a project', async () => { name: 'folderA', qualifier: ComponentQualifier.Directory, }), - measures: generateMeasures('2.0'), ancestors: [component], children: [], }, @@ -226,13 +218,18 @@ it('should correctly show measures for a project', async () => { component: mockComponent({ key: 'index.tsx', name: 'index.tsx', + qualifier: ComponentQualifier.File, }), - measures: [], ancestors: [component], children: [], }, ], }); + componentsHandler.registerComponentMeasures({ + foo: { [MetricKey.ncloc]: mockMeasure({ metric: MetricKey.ncloc }) }, + folderA: generateMeasures('2.0'), + 'index.tsx': {}, + }); const ui = getPageObject(userEvent.setup()); renderCode(); await ui.appLoaded(component.name); @@ -240,13 +237,13 @@ it('should correctly show measures for a project', async () => { // Folder A const folderRow = ui.measureRow(/folderA/).get(); [ - ['ncloc', '2'], - ['bugs', '2'], - ['vulnerabilities', '2'], - ['code_smells', '2'], - ['security_hotspots', '2'], - ['coverage', '2.0%'], - ['duplicated_lines_density', '2.0%'], + [MetricKey.ncloc, '2'], + [MetricKey.bugs, '2'], + [MetricKey.vulnerabilities, '2'], + [MetricKey.code_smells, '2'], + [MetricKey.security_hotspots, '2'], + [MetricKey.coverage, '2.0%'], + [MetricKey.duplicated_lines_density, '2.0%'], ].forEach(([domain, value]) => { expect(ui.measureValueCell(folderRow, domain, value)).toBeInTheDocument(); }); @@ -254,13 +251,13 @@ it('should correctly show measures for a project', async () => { // index.tsx const fileRow = ui.measureRow(/index\.tsx/).get(); [ - ['ncloc', '—'], - ['bugs', '—'], - ['vulnerabilities', '—'], - ['code_smells', '—'], - ['security_hotspots', '—'], - ['coverage', '—'], - ['duplicated_lines_density', '—'], + [MetricKey.ncloc, '—'], + [MetricKey.bugs, '—'], + [MetricKey.vulnerabilities, '—'], + [MetricKey.code_smells, '—'], + [MetricKey.security_hotspots, '—'], + [MetricKey.coverage, '—'], + [MetricKey.duplicated_lines_density, '—'], ].forEach(([domain, value]) => { expect(ui.measureValueCell(fileRow, domain, value)).toBeInTheDocument(); }); @@ -275,7 +272,6 @@ it('should correctly show new VS overall measures for Portfolios', async () => { }); componentsHandler.registerComponentTree({ component, - measures: generateMeasures('1.0', '2.0'), ancestors: [], children: [ { @@ -284,7 +280,6 @@ it('should correctly show new VS overall measures for Portfolios', async () => { key: 'child1', name: 'Child 1', }), - measures: generateMeasures('2.0', '3.0'), ancestors: [component], children: [], }, @@ -293,14 +288,22 @@ it('should correctly show new VS overall measures for Portfolios', async () => { key: 'child2', name: 'Child 2', }), - measures: [ - mockMeasure({ metric: MetricKey.alert_status, value: 'ERROR', period: undefined }), - ], ancestors: [component], children: [], }, ], }); + componentsHandler.registerComponentMeasures({ + portfolio: generateMeasures('1.0', '2.0'), + child1: generateMeasures('2.0', '3.0'), + child2: { + [MetricKey.alert_status]: mockMeasure({ + metric: MetricKey.alert_status, + value: 'ERROR', + period: undefined, + }), + }, + }); const ui = getPageObject(userEvent.setup()); renderCode({ component }); await ui.appLoaded(component.name); @@ -440,20 +443,45 @@ function getPageObject(user: UserEvent) { } function generateMeasures(overallValue = '1.0', newValue = '2.0') { - return [ - ...Object.values(MetricKey) - .filter((metric) => metric !== MetricKey.alert_status) - .map((metric) => + return keyBy( + [ + ...[ + MetricKey.ncloc, + MetricKey.new_lines, + MetricKey.bugs, + MetricKey.new_bugs, + MetricKey.vulnerabilities, + MetricKey.new_vulnerabilities, + MetricKey.code_smells, + MetricKey.new_code_smells, + MetricKey.security_hotspots, + MetricKey.new_security_hotspots, + MetricKey.coverage, + MetricKey.new_coverage, + MetricKey.duplicated_lines_density, + MetricKey.new_duplicated_lines_density, + MetricKey.releasability_rating, + MetricKey.reliability_rating, + MetricKey.new_reliability_rating, + MetricKey.sqale_rating, + MetricKey.new_maintainability_rating, + MetricKey.security_rating, + MetricKey.new_security_rating, + MetricKey.security_review_rating, + MetricKey.new_security_review_rating, + ].map((metric) => isDiffMetric(metric) ? mockMeasure({ metric, period: { index: 1, value: newValue } }) : mockMeasure({ metric, value: overallValue, period: undefined }) ), - mockMeasure({ - metric: MetricKey.alert_status, - value: overallValue === '1.0' || overallValue === '2.0' ? 'OK' : 'ERROR', - period: undefined, - }), - ]; + mockMeasure({ + metric: MetricKey.alert_status, + value: overallValue === '1.0' || overallValue === '2.0' ? 'OK' : 'ERROR', + period: undefined, + }), + ], + 'metric' + ); } function renderCode({ diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/SourceViewerWrapper-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/SourceViewerWrapper-test.tsx index da588a56df5..808ffd35c4a 100644 --- a/server/sonar-web/src/main/js/apps/code/components/__tests__/SourceViewerWrapper-test.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/SourceViewerWrapper-test.tsx @@ -26,13 +26,6 @@ import { mockLocation } from '../../../../helpers/testMocks'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import SourceViewerWrapper, { SourceViewerWrapperProps } from '../SourceViewerWrapper'; -jest.mock('../../../../api/components'); -jest.mock('../../../../api/issues'); -// The following 2 mocks are needed, because IssuesServiceMock mocks more than it should. -// This should be removed once IssuesServiceMock is cleaned up. -jest.mock('../../../../api/rules'); -jest.mock('../../../../api/users'); - const issuesHandler = new IssuesServiceMock(); const componentsHandler = new ComponentsServiceMock(); // eslint-disable-next-line testing-library/no-node-access diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx b/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx new file mode 100644 index 00000000000..e03cf2668b9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/__tests__/ComponentMeasures-it.tsx @@ -0,0 +1,485 @@ +/* + * 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 { act, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { times } from 'lodash'; +import selectEvent from 'react-select-event'; +import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'; +import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock'; +import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock'; +import { MeasuresServiceMock } from '../../../api/mocks/MeasuresServiceMock'; +import { mockPullRequest } from '../../../helpers/mocks/branch-like'; +import { mockComponent } from '../../../helpers/mocks/component'; +import { mockMeasure, mockMetric } from '../../../helpers/testMocks'; +import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils'; +import { ComponentContextShape, ComponentQualifier } from '../../../types/component'; +import { MetricKey } from '../../../types/metrics'; +import routes from '../routes'; + +jest.mock('../../../api/metrics', () => { + const { DEFAULT_METRICS } = jest.requireActual('../../../helpers/mocks/metrics'); + const metrics = Object.values(MetricKey).map( + (key) => DEFAULT_METRICS[key] ?? mockMetric({ key }) + ); + return { + getAllMetrics: jest.fn().mockResolvedValue(metrics), + }; +}); + +const componentsHandler = new ComponentsServiceMock(); +const measuresHandler = new MeasuresServiceMock(); +const issuesHandler = new IssuesServiceMock(); + +afterEach(() => { + componentsHandler.reset(); + measuresHandler.reset(); + issuesHandler.reset(); +}); + +describe('rendering', () => { + it('should correctly render the default overview', async () => { + const { ui } = getPageObject(); + renderMeasuresApp(); + await ui.appLoaded(); + + expect(ui.seeDataAsListLink.get()).toBeInTheDocument(); + expect(ui.overviewFacetBtn.get()).toBeChecked(); + expect(ui.bubbleChart.get()).toBeInTheDocument(); + expect(within(ui.bubbleChart.get()).getAllByRole('link')).toHaveLength(8); + expect(ui.newCodePeriodTxt.get()).toBeInTheDocument(); + + // TODO: check all child facets? + expect(ui.reliabilityFacetBtn.get()).toBeInTheDocument(); + expect(ui.securityFacetBtn.get()).toBeInTheDocument(); + expect(ui.securityReviewFacetBtn.get()).toBeInTheDocument(); + expect(ui.maintainabilityFacetBtn.get()).toBeInTheDocument(); + expect(ui.coverageFacetBtn.get()).toBeInTheDocument(); + expect(ui.duplicationsFacetBtn.get()).toBeInTheDocument(); + expect(ui.sizeFacetBtn.get()).toBeInTheDocument(); + expect(ui.complexityFacetBtn.get()).toBeInTheDocument(); + expect(ui.issuesFacetBtn.get()).toBeInTheDocument(); + }); + + it('should correctly render a list view', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=list'); + await ui.appLoaded(); + + expect(ui.measuresTable.get()).toBeInTheDocument(); + expect(ui.measuresRows.getAll()).toHaveLength(8); + }); + + it('should correctly render a tree view', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=tree'); + await ui.appLoaded(); + + expect(ui.measuresTable.get()).toBeInTheDocument(); + expect(ui.measuresRows.getAll()).toHaveLength(7); + }); + + it('should correctly render a treemap view', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=sqale_rating&view=treemap'); + await ui.appLoaded(); + + expect(ui.treeMapCells.getAll()).toHaveLength(7); + expect(ui.treeMapCell(/folderA C metric\.sqale_rating\.name/).get()).toBeInTheDocument(); + expect(ui.treeMapCell(/test1\.js B metric\.sqale_rating\.name/).get()).toBeInTheDocument(); + expect(ui.treeMapCell(/index\.tsx A metric\.sqale_rating\.name/).get()).toBeInTheDocument(); + }); + + it('should render correctly for an unknown metric', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=unknown'); + await ui.appLoaded(); + + // Fall back to a known metric. + expect(screen.getAllByText('Releasability rating').length).toBeGreaterThan(0); + }); + + it('should render correctly if there are no measures', async () => { + componentsHandler.registerComponentMeasures({}); + measuresHandler.registerComponentMeasures({}); + const { ui } = getPageObject(); + renderMeasuresApp(); + await ui.appLoaded(); + + expect(ui.emptyText.get()).toBeInTheDocument(); + }); + + it('should render correctly if on a pull request and viewing coverage', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=coverage&pullRequest=1', { + branchLike: mockPullRequest({ key: '1' }), + }); + await ui.appLoaded(); + + expect(ui.detailsUnavailableText.get()).toBeInTheDocument(); + }); + + it('should render a warning message if the user does not have access to all components', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=code_smells', { + component: mockComponent({ + key: 'foo', + qualifier: ComponentQualifier.Portfolio, + canBrowseAllChildProjects: false, + }), + }); + await ui.appLoaded(); + + expect( + within(ui.noAccessWarning.get()).getByText('component_measures.not_all_measures_are_shown') + ).toBeInTheDocument(); + }); + + it('should correctly render the language distribution', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=ncloc'); + await ui.appLoaded(); + + expect(screen.getByText('10short_number_suffix.k')).toBeInTheDocument(); + expect(screen.getByText('java')).toBeInTheDocument(); + expect(screen.getByText('5short_number_suffix.k')).toBeInTheDocument(); + expect(screen.getByText('javascript')).toBeInTheDocument(); + expect(screen.getByText('1short_number_suffix.k')).toBeInTheDocument(); + expect(screen.getByText('css')).toBeInTheDocument(); + }); + + it('should only show the best values in list mode', async () => { + const tree = componentsHandler.findComponentTree('foo'); + /* eslint-disable-next-line jest/no-conditional-in-test */ + if (!tree) { + throw new Error('Could not find base tree'); + } + const measures = measuresHandler.getComponentMeasures(); + + /* eslint-disable-next-line testing-library/no-node-access */ + tree.children.push( + ...times(100, (n) => ({ + component: mockComponent({ + key: `foo:file${n}`, + name: `file${n}`, + qualifier: ComponentQualifier.File, + }), + ancestors: [tree.component], + children: [], + })) + ); + componentsHandler.registerComponentTree(tree); + measuresHandler.registerComponentMeasures( + times(100, (n) => `foo:file${n}`).reduce((acc, key) => { + acc[key] = { + [MetricKey.sqale_rating]: mockMeasure({ + metric: MetricKey.sqale_rating, + value: '1.0', + bestValue: true, + }), + }; + return acc; + }, measures) + ); + + const { ui, user } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=sqale_rating&view=list'); + await ui.appLoaded(); + + await user.click(ui.maintainabilityFacetBtn.get()); + await user.click(ui.metricBtn('Maintainability Rating').get()); + await ui.changeViewToList(); + + expect(ui.notShowingAllComponentsTxt.get()).toBeInTheDocument(); + await user.click(ui.showAllBtn.get()); + expect(ui.notShowingAllComponentsTxt.query()).not.toBeInTheDocument(); + }); +}); + +describe('navigation', () => { + it('should be able to drilldown through the file structure', async () => { + const { ui, user } = getPageObject(); + renderMeasuresApp(); + await ui.appLoaded(); + + // Drilldown to the file level. + await user.click(ui.maintainabilityFacetBtn.get()); + + await user.click(ui.metricBtn('Code Smells').get()); + expect( + within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '3' }) + ).toBeInTheDocument(); + expect( + within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }) + ).toBeInTheDocument(); + + await user.click(ui.fileLink('foo:folderA').get()); + expect( + within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }) + ).toBeInTheDocument(); + expect( + within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }) + ).toBeInTheDocument(); + + await user.click(ui.fileLink('foo:folderA/out.tsx').get()); + expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0); + + // Go back using the breadcrumbs. + await user.click(ui.breadcrumbLink('folderA').get()); + expect(ui.measuresRow('out.tsx').get()).toBeInTheDocument(); + expect(ui.measuresRow('in.tsx').get()).toBeInTheDocument(); + }); + + it('should be able to drilldown thanks to a files list', async () => { + const { ui, user } = getPageObject(); + renderMeasuresApp(); + await ui.appLoaded(); + + await user.click(ui.maintainabilityFacetBtn.get()); + await user.click(ui.metricBtn('Code Smells').get()); + await ui.changeViewToList(); + + expect( + within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }) + ).toBeInTheDocument(); + expect( + within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }) + ).toBeInTheDocument(); + + await user.click(ui.fileLink('foo:folderA/out.tsx').get()); + expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0); + }); + + it('should be able to drilldown thanks to a tree map', async () => { + const { ui, user } = getPageObject(); + renderMeasuresApp(); + await ui.appLoaded(); + + await user.click(ui.maintainabilityFacetBtn.get()); + await user.click(ui.metricBtn('Maintainability Rating').get()); + await ui.changeViewToTreeMap(); + + expect(ui.treeMapCell(/folderA/).get()).toBeInTheDocument(); + expect(ui.treeMapCell(/test1\.js/).get()).toBeInTheDocument(); + + // TODO: once the new design is live, change this to target a link rather than clicking on some text. + await user.click(ui.treeMapCell(/folderA/).get()); + + expect(ui.treeMapCell(/out\.tsx/).get()).toBeInTheDocument(); + expect(ui.treeMapCell(/in\.tsx/).get()).toBeInTheDocument(); + + // TODO: once the new design is live, change this to target a link rather than clicking on some text. + await user.click(ui.treeMapCell(/out.tsx/).get()); + expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0); + }); + + it('should be able to drilldown using the keyboard', async () => { + const { ui, user } = getPageObject(); + renderMeasuresApp(); + await ui.appLoaded(); + + // Drilldown to the file level. + await user.click(ui.maintainabilityFacetBtn.get()); + await user.click(ui.metricBtn('Code Smells').get()); + + // Select "folderA". + await ui.arrowDown(); + await act(async () => { + await ui.arrowRight(); + }); + + expect( + within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }) + ).toBeInTheDocument(); + expect( + within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }) + ).toBeInTheDocument(); + + // Move back to project. + await act(async () => { + await ui.arrowLeft(); + }); + + expect( + within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '3' }) + ).toBeInTheDocument(); + + // Go to "folderA/out.tsx". + await act(async () => { + await ui.arrowRight(); + }); + await ui.arrowDown(); + await ui.arrowDown(); + await act(async () => { + await ui.arrowRight(); + }); + + expect((await ui.sourceCode.findAll()).length).toBeGreaterThan(0); + expect(screen.getAllByText('out.tsx').length).toBeGreaterThan(0); + }); +}); + +describe('redirects', () => { + it('should redirect old history route', () => { + renderMeasuresApp('component_measures/metric/bugs/history?id=foo'); + expect( + screen.getByText('/project/activity?id=foo&graph=custom&custom_metrics=bugs') + ).toBeInTheDocument(); + }); + + it('should redirect old metric route', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures/metric/bugs'); + await ui.appLoaded(); + expect(ui.metricBtn('Bugs').get()).toBeChecked(); + }); + + it('should redirect old domain route', async () => { + const { ui } = getPageObject(); + renderMeasuresApp('component_measures/domain/bugs'); + await ui.appLoaded(); + expect(ui.reliabilityFacetBtn.get()).toHaveAttribute('aria-expanded', 'true'); + }); +}); + +it('should allow to load more components', async () => { + const tree = componentsHandler.findComponentTree('foo'); + /* eslint-disable-next-line jest/no-conditional-in-test */ + if (!tree) { + throw new Error('Could not find base tree'); + } + + /* eslint-disable-next-line testing-library/no-node-access */ + tree.children.push( + ...times(1000, (n) => ({ + component: mockComponent({ + key: `foo:file${n}`, + name: `file${n}`, + qualifier: ComponentQualifier.File, + }), + ancestors: [tree.component], + children: [], + })) + ); + componentsHandler.registerComponentTree(tree); + + const { ui, user } = getPageObject(); + renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=list'); + await ui.appLoaded(); + await user.click(ui.showAllBtn.get()); + + expect(ui.showingOutOfTxt('500', '1,008').get()).toBeInTheDocument(); + await ui.clickLoadMore(); + expect(ui.showingOutOfTxt('1,000', '1,008').get()).toBeInTheDocument(); +}); + +// TODO: +// - activity links +// - sidebar facet values (issue count, rating, etc) + +function getPageObject() { + const user = userEvent.setup(); + + const selectors = { + // Overview + seeDataAsListLink: byRole('link', { name: 'component_measures.overview.see_data_as_list' }), + bubbleChart: byTestId('bubble-chart'), + newCodePeriodTxt: byText( + 'overview.new_code_period_x.overview.period.previous_version_only_date' + ), + + // Facets + overviewFacetBtn: byRole('checkbox', { + name: 'component_measures.overview.project_overview.facet', + }), + releasabilityFacetBtn: byRole('button', { name: 'Releasability' }), + reliabilityFacetBtn: byRole('button', { name: 'Reliability' }), + securityFacetBtn: byRole('button', { name: 'Security' }), + securityReviewFacetBtn: byRole('button', { name: 'SecurityReview' }), + maintainabilityFacetBtn: byRole('button', { name: 'Maintainability' }), + coverageFacetBtn: byRole('button', { name: 'Coverage' }), + duplicationsFacetBtn: byRole('button', { name: 'Duplications' }), + sizeFacetBtn: byRole('button', { name: 'Size' }), + complexityFacetBtn: byRole('button', { name: 'Complexity' }), + issuesFacetBtn: byRole('button', { name: 'Issues' }), + metricBtn: (name: string) => byRole('checkbox', { name }), + + // Measure content + measuresTable: byRole('table'), + measuresRows: byRole('row'), + measuresRow: (name: string) => byRole('row', { name: new RegExp(name) }), + treeMapCells: byRole('treeitem'), + treeMapCell: (name: string | RegExp) => byRole('treeitem', { name }), + fileLink: (name: string) => byRole('link', { name }), + sourceCode: byText('function Test() {}'), + notShowingAllComponentsTxt: byText(/component_measures.hidden_best_score_metrics/), + + // Misc + loading: byLabelText('loading'), + breadcrumbLink: (name: string) => byRole('link', { name }), + viewSelect: byLabelText('component_measures.view_as'), + emptyText: byText('component_measures.empty'), + detailsUnavailableText: byText('component_measures.details_are_not_available'), + noAccessWarning: byRole('alert'), + showingOutOfTxt: (x: string, y: string) => byText(`x_of_y_shown.${x}.${y}`), + showAllBtn: byRole('button', { name: 'show_them' }), + }; + + const ui = { + ...selectors, + + async appLoaded() { + await waitFor(() => { + expect(selectors.loading.query()).not.toBeInTheDocument(); + }); + }, + async changeViewToList() { + await selectEvent.select(ui.viewSelect.get(), 'component_measures.tab.list'); + }, + async changeViewToTreeMap() { + await selectEvent.select(ui.viewSelect.get(), 'component_measures.tab.treemap'); + }, + async arrowDown() { + await user.keyboard('[ArrowDown]'); + }, + async arrowRight() { + await user.keyboard('[ArrowRight]'); + }, + async arrowLeft() { + await user.keyboard('[ArrowLeft]'); + }, + async clickLoadMore() { + await user.click(screen.getByRole('button', { name: 'show_more' })); + }, + }; + + return { + ui, + user, + }; +} + +function renderMeasuresApp(navigateTo?: string, componentContext?: Partial) { + return renderAppWithComponentContext( + 'component_measures', + routes, + { navigateTo }, + { component: mockComponent({ key: 'foo' }), ...componentContext } + ); +} diff --git a/server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx b/server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx deleted file mode 100644 index b1ce4b0ea0c..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/__tests__/MeasuresApp-it.tsx +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { screen } from '@testing-library/react'; -import { renderAppRoutes } from '../../../helpers/testReactTestingUtils'; -import routes from '../routes'; - -it('should redirect old history route', () => { - renderMeasuresApp('component_measures/metric/bugs/history'); - - expect( - screen.getByText('/project/activity?graph=custom&custom_metrics=bugs') - ).toBeInTheDocument(); -}); - -function renderMeasuresApp(navigateTo?: string) { - renderAppRoutes('component_measures', routes, { navigateTo }); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx index 36f66810afc..a99b3684ad6 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx @@ -84,7 +84,7 @@ interface State { metrics: Dict; } -export class ComponentMeasuresApp extends React.PureComponent { +class ComponentMeasuresApp extends React.PureComponent { mounted = false; state: State; @@ -277,7 +277,7 @@ export class ComponentMeasuresApp extends React.PureComponent { if (this.state.loading) { return (
- +
); } diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx deleted file mode 100644 index eab9fec266d..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumb-test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import Breadcrumb from '../Breadcrumb'; - -it('should show the last element without clickable link', () => { - expect( - shallow( - {}} - isLast={true} - /> - ) - ).toMatchSnapshot(); -}); - -it('should correctly show a middle element', () => { - expect( - shallow( - {}} - isLast={false} - /> - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx deleted file mode 100644 index 8c5f81b6c2d..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/Breadcrumbs-test.tsx +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 { mount, shallow } from 'enzyme'; -import * as React from 'react'; -import { getBreadcrumbs } from '../../../../api/components'; -import { KeyboardKeys } from '../../../../helpers/keycodes'; -import { keydown, waitAndUpdate } from '../../../../helpers/testUtils'; -import Breadcrumbs from '../Breadcrumbs'; - -jest.mock('../../../../api/components', () => ({ - getBreadcrumbs: jest.fn().mockResolvedValue([ - { key: 'anc1', name: 'Ancestor1' }, - { key: 'anc2', name: 'Ancestor2' }, - { key: 'bar', name: 'Bar' }, - ]), -})); - -const componentFoo = { - key: 'foo', - name: 'Foo', - qualifier: 'TRK', -}; - -const componentBar = { - key: 'bar', - name: 'Bar', - qualifier: 'TRK', -}; - -beforeEach(() => { - (getBreadcrumbs as jest.Mock).mockClear(); -}); - -it('should display correctly for the list view', () => { - const wrapper = mount( - {}} - rootComponent={componentFoo} - /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should display only the root component', () => { - const wrapper = shallowRender({ component: componentFoo }); - expect(wrapper.state()).toMatchSnapshot(); -}); - -it('should load the breadcrumb from the api', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(getBreadcrumbs).toHaveBeenCalled(); -}); - -it('should correctly handle keyboard action', async () => { - const handleSelect = jest.fn(); - const wrapper = shallowRender({ handleSelect }); - await waitAndUpdate(wrapper); - keydown({ key: KeyboardKeys.LeftArrow }); - expect(handleSelect).toHaveBeenCalled(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - {}} - rootComponent={componentFoo} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/ComponentMeasuresApp-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/ComponentMeasuresApp-test.tsx deleted file mode 100644 index f7751cebc97..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/ComponentMeasuresApp-test.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { getMeasuresWithPeriod } from '../../../../api/measures'; -import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper'; -import { Alert } from '../../../../components/ui/Alert'; -import { mockMainBranch, mockPullRequest } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockIssue, mockLocation, mockRouter } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { ComponentQualifier } from '../../../../types/component'; -import { ComponentMeasuresApp } from '../ComponentMeasuresApp'; - -jest.mock('../../../../api/metrics', () => ({ - getAllMetrics: jest.fn().mockResolvedValue([ - { - id: '1', - key: 'lines_to_cover', - type: 'INT', - name: 'Lines to Cover', - domain: 'Coverage', - }, - { - id: '2', - key: 'coverage', - type: 'PERCENT', - name: 'Coverage', - domain: 'Coverage', - }, - { - id: '3', - key: 'duplicated_lines_density', - type: 'PERCENT', - name: 'Duplicated Lines (%)', - domain: 'Duplications', - }, - { - id: '4', - key: 'new_bugs', - type: 'INT', - name: 'New Bugs', - domain: 'Reliability', - }, - ]), -})); - -jest.mock('../../../../api/measures', () => ({ - getMeasuresWithPeriod: jest.fn(), -})); - -beforeEach(() => { - (getMeasuresWithPeriod as jest.Mock).mockResolvedValue({ - component: { measures: [{ metric: 'coverage', value: '80.0' }] }, - period: { mode: 'previous_version' }, - }); -}); - -it('should render correctly', async () => { - const wrapper = shallowRender(); - expect(wrapper.find('.spinner')).toHaveLength(1); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render a measure overview', async () => { - const wrapper = shallowRender({ - location: mockLocation({ pathname: '/component_measures', query: { metric: 'Reliability' } }), - }); - expect(wrapper.find('.spinner')).toHaveLength(1); - await waitAndUpdate(wrapper); - expect(wrapper.find('MeasureOverviewContainer')).toHaveLength(1); -}); - -it('should render a message when there are no measures', async () => { - (getMeasuresWithPeriod as jest.Mock).mockResolvedValue({ - component: { measures: [] }, - period: { mode: 'previous_version' }, - }); - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should not render drilldown for estimated duplications', async () => { - const wrapper = shallowRender({ branchLike: mockPullRequest({ title: '' }) }); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should refresh branch status if issues are updated', async () => { - const fetchBranchStatus = jest.fn(); - const branchLike = mockPullRequest(); - const wrapper = shallowRender({ branchLike, fetchBranchStatus }); - const instance = wrapper.instance(); - await waitAndUpdate(wrapper); - - instance.handleIssueChange(mockIssue()); - expect(fetchBranchStatus).toHaveBeenCalledWith(branchLike, 'foo'); -}); - -it('should render a warning message when user does not have access to all projects whithin a Portfolio', async () => { - const wrapper = shallowRender({ - component: mockComponent({ - qualifier: ComponentQualifier.Portfolio, - canBrowseAllChildProjects: false, - }), - }); - await waitAndUpdate(wrapper); - expect(wrapper.find(ScreenPositionHelper).dive()).toMatchSnapshot( - 'Measure menu with warning (ScreenPositionHelper)' - ); -}); - -it.each([ - [ComponentQualifier.Portfolio, true, false], - [ComponentQualifier.Project, false, false], - [ComponentQualifier.Portfolio, false, true], -])( - 'should not render a warning message', - async ( - componentQualifier: ComponentQualifier, - canBrowseAllChildProjects: boolean, - alertIsVisible: boolean - ) => { - const wrapper = shallowRender({ - component: mockComponent({ - qualifier: componentQualifier, - canBrowseAllChildProjects, - }), - }); - await waitAndUpdate(wrapper); - expect(wrapper.find(ScreenPositionHelper).dive().find(Alert).exists()).toBe(alertIsVisible); - } -); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx deleted file mode 100644 index 987dd463d0e..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureContent-test.tsx +++ /dev/null @@ -1,209 +0,0 @@ -/* - * 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { getComponentTree } from '../../../../api/components'; -import { mockComponentMeasure } from '../../../../helpers/mocks/component'; -import { mockRouter } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import MeasureContent from '../MeasureContent'; - -jest.mock('../../../../api/components', () => { - const { mockComponentMeasure } = jest.requireActual('../../../../helpers/mocks/component'); - return { - getComponentTree: jest.fn().mockResolvedValue({ - paging: { pageIndex: 1, pageSize: 500, total: 2 }, - baseComponent: mockComponentMeasure(), - components: [mockComponentMeasure(true)], - metrics: [ - { - bestValue: '0', - custom: false, - description: 'Bugs', - domain: 'Reliability', - hidden: false, - higherValuesAreBetter: false, - key: 'bugs', - name: 'Bugs', - qualitative: true, - type: 'INT', - }, - ], - }), - }; -}); - -jest.mock('../../../../api/measures', () => ({ - getMeasures: jest.fn().mockResolvedValue([{ metric: 'bugs', value: '12', bestValue: false }]), -})); - -const METRICS = { - bugs: { id: '1', key: 'bugs', type: 'INT', name: 'Bugs', domain: 'Reliability' }, -}; - -const WINDOW_HEIGHT = 800; -const originalHeight = window.innerHeight; - -beforeEach(() => { - jest.clearAllMocks(); -}); - -beforeAll(() => { - Object.defineProperty(window, 'innerHeight', { - writable: true, - configurable: true, - value: WINDOW_HEIGHT, - }); -}); - -afterAll(() => { - Object.defineProperty(window, 'innerHeight', { - writable: true, - configurable: true, - value: originalHeight, - }); -}); - -it('should render correctly for a project', async () => { - const wrapper = shallowRender(); - expect(wrapper.type()).toBeNull(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render correctly when asc prop is defined', async () => { - const wrapper = shallowRender({ asc: true }); - expect(wrapper.type()).toBeNull(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render correctly when view prop is tree', async () => { - const wrapper = shallowRender({ view: 'tree' }); - expect(wrapper.type()).toBeNull(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render correctly for a file', async () => { - (getComponentTree as jest.Mock).mockResolvedValueOnce({ - paging: { pageIndex: 1, pageSize: 500, total: 0 }, - baseComponent: mockComponentMeasure(true), - components: [], - metrics: [METRICS.bugs], - }); - const wrapper = shallowRender(); - expect(wrapper.type()).toBeNull(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should test fetchMoreComponents to work correctly', async () => { - (getComponentTree as jest.Mock).mockResolvedValueOnce({ - paging: { pageIndex: 12, pageSize: 500, total: 0 }, - baseComponent: mockComponentMeasure(false), - components: [], - metrics: [METRICS.bugs], - }); - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.instance().fetchMoreComponents(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should test getComponentRequestParams response for different arguments', () => { - const wrapper = shallowRender({ asc: false }); - const metric = { - direction: -1, - key: 'new_reliability_rating', - }; - const reqParamsList = { - metricKeys: ['new_reliability_rating'], - opts: { - additionalFields: 'metrics', - asc: true, - metricPeriodSort: 1, - metricSort: 'new_reliability_rating', - metricSortFilter: 'withMeasuresOnly', - ps: 500, - s: 'metricPeriod', - }, - strategy: 'leaves', - }; - expect(wrapper.instance().getComponentRequestParams('list', metric, { asc: true })).toEqual( - reqParamsList - ); - // when options.asc is not passed the opts.asc will take the default value - reqParamsList.opts.asc = false; - expect(wrapper.instance().getComponentRequestParams('list', metric, {})).toEqual(reqParamsList); - - const reqParamsTreeMap = { - metricKeys: ['new_reliability_rating', 'new_lines'], - opts: { - additionalFields: 'metrics', - asc: true, - metricPeriodSort: 1, - metricSort: 'new_lines', - metricSortFilter: 'withMeasuresOnly', - ps: 500, - s: 'metricPeriod', - }, - strategy: 'children', - }; - expect(wrapper.instance().getComponentRequestParams('treemap', metric, { asc: true })).toEqual( - reqParamsTreeMap - ); - // when options.asc is not passed the opts.asc will take the default value - reqParamsTreeMap.opts.asc = false; - expect(wrapper.instance().getComponentRequestParams('treemap', metric, {})).toEqual( - reqParamsTreeMap - ); - - const reqParamsTree = { - metricKeys: ['new_reliability_rating'], - opts: { - additionalFields: 'metrics', - asc: false, - ps: 500, - s: 'qualifier,name', - }, - strategy: 'children', - }; - expect(wrapper.instance().getComponentRequestParams('tree', metric, { asc: false })).toEqual( - reqParamsTree - ); - // when options.asc is not passed the opts.asc will take the default value - reqParamsTree.opts.asc = true; - expect(wrapper.instance().getComponentRequestParams('tree', metric, {})).toEqual(reqParamsTree); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx deleted file mode 100644 index 80f3025a6b1..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureHeader-test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockBranch } from '../../../../helpers/mocks/branch-like'; -import { Period } from '../../../../types/types'; -import MeasureHeader from '../MeasureHeader'; - -const METRIC = { - id: '1', - key: 'reliability_rating', - type: 'RATING', - name: 'Reliability Rating', -}; - -const LEAK_METRIC = { - id: '2', - key: 'new_reliability_rating', - type: 'RATING', - name: 'Reliability Rating on New Code', -}; - -const LEAK_MEASURE = '3.0'; - -const SECONDARY = { - value: 'java=175123;js=26382', - metric: 'ncloc_language_distribution', -}; - -const PROPS = { - component: { key: 'foo', name: 'Foo', qualifier: 'TRK' }, - leakPeriod: { - date: '2017-05-16T13:50:02+0200', - index: 1, - mode: 'previous_version', - parameter: '6,4', - } as Period, - measureValue: '3.0', - metric: METRIC, -}; - -it('should render correctly', () => { - expect(shallow()).toMatchSnapshot(); -}); - -it('should render correctly for leak', () => { - expect( - shallow() - ).toMatchSnapshot(); -}); - -it('should render with a branch', () => { - const branch = mockBranch({ name: 'feature' }); - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); - -it('should not render link to activity page for files', () => { - expect( - shallow() - .find('HistoryIcon') - .exists() - ).toBe(true); - - expect( - shallow() - .find('HistoryIcon') - .exists() - ).toBe(false); -}); - -it('should display secondary measure too', () => { - const wrapper = shallow(); - expect(wrapper.find('withLanguagesContext(LanguageDistribution)')).toHaveLength(1); -}); - -it('should work with measure without value', () => { - expect(shallow()).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx deleted file mode 100644 index 4a738981f1a..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureOverview-test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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 { shallow } from 'enzyme'; -import { keyBy } from 'lodash'; -import * as React from 'react'; -import { getComponentLeaves } from '../../../../api/components'; -import { mockBranch } from '../../../../helpers/mocks/branch-like'; -import { - mockComponentMeasure, - mockComponentMeasureEnhanced, -} from '../../../../helpers/mocks/component'; -import { - mockMeasure, - mockMeasureEnhanced, - mockMetric, - mockPeriod, -} from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { MetricKey } from '../../../../types/metrics'; -import { BUBBLES_FETCH_LIMIT } from '../../utils'; -import MeasureOverview from '../MeasureOverview'; - -jest.mock('../../../../api/components', () => ({ - getComponentLeaves: jest - .fn() - .mockResolvedValue({ components: [], paging: { total: 200, pageIndex: 1, pageSize: 100 } }), -})); - -beforeEach(jest.clearAllMocks); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); - expect(shallowRender({ leakPeriod: mockPeriod(), branchLike: mockBranch() })).toMatchSnapshot( - 'has leak period' - ); - expect(shallowRender({ component: mockComponentMeasure(true) })).toMatchSnapshot('is file'); -}); - -it('should correctly enhance leaf components', async () => { - (getComponentLeaves as jest.Mock).mockResolvedValueOnce({ - components: [ - mockComponentMeasure(false, { measures: [mockMeasure({ metric: MetricKey.bugs })] }), - ], - }); - const updateLoading = jest.fn(); - const wrapper = shallowRender({ updateLoading }); - - expect(updateLoading).toHaveBeenCalledWith({ bubbles: true }); - expect(getComponentLeaves).toHaveBeenCalledWith( - 'foo', - [ - MetricKey.ncloc, - MetricKey.reliability_remediation_effort, - MetricKey.bugs, - MetricKey.reliability_rating, - ], - expect.objectContaining({ metricSort: MetricKey.bugs, s: 'metric', ps: BUBBLES_FETCH_LIMIT }) - ); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().components).toEqual([ - mockComponentMeasureEnhanced({ - measures: [ - mockMeasureEnhanced({ - leak: '1.0', - metric: mockMetric({ key: MetricKey.bugs, type: 'INT' }), - }), - ], - }), - ]); - expect(updateLoading).toHaveBeenLastCalledWith({ bubbles: false }); -}); - -it('should not enhance file components', () => { - shallowRender({ component: mockComponentMeasure(true) }); - expect(getComponentLeaves).not.toHaveBeenCalled(); -}); - -it('should correctly flag itself as (un)mounted', () => { - const wrapper = shallowRender(); - const instance = wrapper.instance(); - expect(instance.mounted).toBe(true); - wrapper.unmount(); - expect(instance.mounted).toBe(false); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - m.key - )} - updateLoading={jest.fn()} - updateSelected={jest.fn()} - rootComponent={mockComponentMeasure()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx deleted file mode 100644 index 9a13cb79145..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/MeasureViewSelect-test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { shallow } from 'enzyme'; -import * as React from 'react'; -import ListIcon from '../../../../components/icons/ListIcon'; -import { mockMetric } from '../../../../helpers/testMocks'; -import { MetricKey } from '../../../../types/metrics'; -import MeasureViewSelect from '../MeasureViewSelect'; - -it('should render correctly', () => { - expect( - shallowRender({ metric: mockMetric({ key: MetricKey.releasability_rating }) }) - ).toMatchSnapshot('has no list'); - expect( - shallowRender({ metric: mockMetric({ key: MetricKey.alert_status, type: 'LEVEL' }) }) - ).toMatchSnapshot('has no tree'); - expect(shallowRender({ metric: mockMetric({ type: 'RATING' }) })).toMatchSnapshot( - 'has no treemap' - ); -}); - -it('should correctly trigger a selection change', () => { - const handleViewChange = jest.fn(); - const wrapper = shallowRender({ handleViewChange }); - wrapper.instance().handleChange({ icon: , label: 'List View', value: 'list' }); - expect(handleViewChange).toHaveBeenCalledWith('list'); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap deleted file mode 100644 index e0db087b0a3..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumb-test.tsx.snap +++ /dev/null @@ -1,27 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should correctly show a middle element 1`] = ` - - - - Foo - - - - -`; - -exports[`should show the last element without clickable link 1`] = ` - - - - Foo - - - -`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap deleted file mode 100644 index 1ecce41d367..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/Breadcrumbs-test.tsx.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display correctly for the list view 1`] = ` - -`; - -exports[`should display only the root component 1`] = ` -{ - "breadcrumbs": [ - { - "key": "foo", - "name": "Foo", - "qualifier": "TRK", - }, - ], -} -`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/ComponentMeasuresApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/ComponentMeasuresApp-test.tsx.snap deleted file mode 100644 index 5799f077206..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/ComponentMeasuresApp-test.tsx.snap +++ /dev/null @@ -1,227 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not render drilldown for estimated duplications 1`] = ` -
- - -
- - - -
-
-
- component_measures.details_are_not_available -
-
-
-
-
-`; - -exports[`should render a message when there are no measures 1`] = ` -
- - - -
-`; - -exports[`should render a warning message when user does not have access to all projects whithin a Portfolio: Measure menu with warning (ScreenPositionHelper) 1`] = ` -
-
-
- - - component_measures.not_all_measures_are_shown - - - -
- -
-
-
-
-`; - -exports[`should render correctly 1`] = ` -
- - -
- - - - -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap deleted file mode 100644 index ba4200d3b46..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureContent-test.tsx.snap +++ /dev/null @@ -1,927 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly for a file 1`] = ` -
- -
-
-
- - } - right={ -
- } - /> -
-
-
-
- -
- -
-
-
-`; - -exports[`should render correctly for a project 1`] = ` -
- -
-
-
- - } - right={ -
- -
- component_measures.view_as -
- - -
-
- } - /> -
-
-
-
- - -
-
-`; - -exports[`should render correctly when asc prop is defined 1`] = ` -
- -
-
-
- - } - right={ -
- -
- component_measures.view_as -
- - -
-
- } - /> -
-
-
-
- - -
-
-`; - -exports[`should render correctly when view prop is tree 1`] = ` -
- -
-
-
- - } - right={ -
- -
- component_measures.view_as -
- - -
-
- } - /> -
-
-
-
- - -
-
-`; - -exports[`should test fetchMoreComponents to work correctly 1`] = ` -
- -
-
-
- - } - right={ -
- -
- component_measures.view_as -
- - -
-
- } - /> -
-
-
-
- - -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap deleted file mode 100644 index 787577a0fa7..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureHeader-test.tsx.snap +++ /dev/null @@ -1,244 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
-
-
- - Reliability Rating - - - - - - - - - - -
-
- -
-
-
-`; - -exports[`should render correctly for leak 1`] = ` -
-
-
- - Reliability Rating on New Code - - - - - -
-
- -
-
-
-`; - -exports[`should render with a branch 1`] = ` -
-
-
- - Reliability Rating on New Code - - - - - -
-
- -
-
-
-`; - -exports[`should work with measure without value 1`] = ` -
-
-
- - Reliability Rating - - - - - - - - - - -
-
- -
-
-
-`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap deleted file mode 100644 index 56095ad8c01..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureOverview-test.tsx.snap +++ /dev/null @@ -1,414 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: default 1`] = ` -
-
- -
-
- - } - right={ - - } - /> -
-
-
-
-
- - -
-
-`; - -exports[`should render correctly: has leak period 1`] = ` -
-
- -
-
- - } - right={ - - } - /> -
-
-
-
-
- -
- - -
-
-`; - -exports[`should render correctly: is file 1`] = ` -
-
- -
-
- - } - right={ - - } - /> -
-
-
-
-
- -
- -
-
-
-`; - -exports[`should render correctly: loading 1`] = ` -
-
- -
-
- - } - right={ - - } - /> -
-
-
-
-
- -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap deleted file mode 100644 index 20414789789..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/__tests__/__snapshots__/MeasureViewSelect-test.tsx.snap +++ /dev/null @@ -1,102 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly: has no list 1`] = ` -, - "label": "component_measures.tab.list", - "value": "list", - }, - ] - } - value={ - { - "icon": , - "label": "component_measures.tab.list", - "value": "list", - } - } -/> -`; - -exports[`should render correctly: has no treemap 1`] = ` -