Browse Source

SONAR-17667 Finish migration of Activity Graph tests to RTL

tags/9.8.0.63668
Wouter Admiraal 1 year ago
parent
commit
a8a5d01b4a
39 changed files with 579 additions and 1947 deletions
  1. 1
    1
      server/sonar-web/src/main/js/apps/overview/styles.css
  2. 1
    1
      server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx
  3. 27
    29
      server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx
  4. 0
    35
      server/sonar-web/src/main/js/components/activity-graph/GraphsLegendNewCode.tsx
  5. 10
    9
      server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx
  6. 29
    22
      server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx
  7. 5
    7
      server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx
  8. 6
    6
      server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx
  9. 12
    12
      server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx
  10. 173
    149
      server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
  11. 21
    4
      server/sonar-web/src/main/js/components/activity-graph/__tests__/DataTableModal-it.tsx
  12. 0
    88
      server/sonar-web/src/main/js/components/activity-graph/__tests__/DefinitionChangeEventInner-test.tsx
  13. 165
    0
      server/sonar-web/src/main/js/components/activity-graph/__tests__/EventInner-it.tsx
  14. 0
    56
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx
  15. 0
    51
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx
  16. 0
    30
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendNewCode-test.tsx
  17. 0
    38
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx
  18. 127
    0
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-it.tsx
  19. 0
    111
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx
  20. 0
    33
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx
  21. 0
    64
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx
  22. 0
    51
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx
  23. 0
    33
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx
  24. 0
    59
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx
  25. 0
    60
      server/sonar-web/src/main/js/components/activity-graph/__tests__/RichQualityGateEventInner-test.tsx
  26. 0
    248
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap
  27. 0
    52
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap
  28. 0
    58
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap
  29. 0
    14
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendNewCode-test.tsx.snap
  30. 0
    22
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap
  31. 0
    226
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap
  32. 0
    25
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap
  33. 0
    71
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap
  34. 0
    45
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap
  35. 0
    60
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap
  36. 0
    34
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap
  37. 0
    139
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap
  38. 2
    2
      server/sonar-web/src/main/js/components/activity-graph/styles.css
  39. 0
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/styles.css View File

@@ -272,7 +272,7 @@
*/

.overview-panel .activity-graph-legends {
text-align: right;
justify-content: right;
margin-top: -30px;
}


+ 1
- 1
server/sonar-web/src/main/js/components/activity-graph/DataTableModal.tsx View File

@@ -41,7 +41,7 @@ export interface DataTableModalProps {

type DataTableEntry = { date: Date } & { [x: string]: string | undefined };

const MAX_DATA_TABLE_ROWS = 100;
export const MAX_DATA_TABLE_ROWS = 100;

export default function DataTableModal(props: DataTableModalProps) {
const { analyses, series, graphEndDate, graphStartDate } = props;

+ 27
- 29
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendCustom.tsx View File

@@ -32,36 +32,34 @@ export interface GraphsLegendCustomProps {
export default function GraphsLegendCustom(props: GraphsLegendCustomProps) {
const { series } = props;
return (
<div className="activity-graph-legends display-flex-center">
<div className="flex-1">
{series.map((serie, idx) => {
const hasData = hasDataValues(serie);
const legendItem = (
<GraphsLegendItem
index={idx}
metric={serie.name}
name={serie.translatedName}
removeMetric={props.removeMetric}
showWarning={!hasData}
/>
);
if (!hasData) {
return (
<Tooltip
key={serie.name}
overlay={translate('project_activity.graphs.custom.metric_no_history')}
>
<span className="spacer-left spacer-right">{legendItem}</span>
</Tooltip>
);
}
<ul className="activity-graph-legends">
{series.map((serie, idx) => {
const hasData = hasDataValues(serie);
const legendItem = (
<GraphsLegendItem
index={idx}
metric={serie.name}
name={serie.translatedName}
removeMetric={props.removeMetric}
showWarning={!hasData}
/>
);
if (!hasData) {
return (
<span className="spacer-left spacer-right" key={serie.name}>
{legendItem}
</span>
<Tooltip
key={serie.name}
overlay={translate('project_activity.graphs.custom.metric_no_history')}
>
<li className="spacer-left spacer-right">{legendItem}</li>
</Tooltip>
);
})}
</div>
</div>
}
return (
<li className="spacer-left spacer-right" key={serie.name}>
{legendItem}
</li>
);
})}
</ul>
);
}

+ 0
- 35
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendNewCode.tsx View File

@@ -1,35 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import Tooltip from '../../components/controls/Tooltip';
import { translate } from '../../helpers/l10n';

export default function GraphsLegendNewCode() {
return (
<Tooltip overlay={translate('project_activity.graphs.new_code_long')}>
<span
aria-label={translate('project_activity.graphs.new_code_long')}
className="activity-graph-new-code-legend display-flex-center pull-right note"
>
{translate('project_activity.graphs.new_code')}
</span>
</Tooltip>
);
}

+ 10
- 9
server/sonar-web/src/main/js/components/activity-graph/GraphsLegendStatic.tsx View File

@@ -27,16 +27,17 @@ export interface GraphsLegendStaticProps {

export default function GraphsLegendStatic({ series }: GraphsLegendStaticProps) {
return (
<div className="activity-graph-legends">
<ul className="activity-graph-legends">
{series.map((serie, idx) => (
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
index={idx}
key={serie.name}
metric={serie.name}
name={serie.translatedName}
/>
<li key={serie.name}>
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
index={idx}
metric={serie.name}
name={serie.translatedName}
/>
</li>
))}
</div>
</ul>
);
}

+ 29
- 22
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltips.tsx View File

@@ -20,7 +20,7 @@
import * as React from 'react';
import { Popup, PopupPlacement } from '../../components/ui/popups';
import { isDefined } from '../../helpers/types';
import { AnalysisEvent, MeasureHistory, Serie } from '../../types/project-activity';
import { AnalysisEvent, GraphType, MeasureHistory, Serie } from '../../types/project-activity';
import DateTimeFormatter from '../intl/DateTimeFormatter';
import GraphsTooltipsContent from './GraphsTooltipsContent';
import GraphsTooltipsContentCoverage from './GraphsTooltipsContentCoverage';
@@ -42,53 +42,60 @@ interface Props {
}

const TOOLTIP_WIDTH = 250;
const TOOLTIP_LEFT_MARGIN = 60;
const TOOLTIP_LEFT_FLIP_THRESHOLD = 50;

export default class GraphsTooltips extends React.PureComponent<Props> {
renderContent() {
const { tooltipIdx } = this.props;
const { tooltipIdx, series, graph, measuresHistory } = this.props;

return this.props.series.map((serie, idx) => {
return series.map((serie, idx) => {
const point = serie.data[tooltipIdx];
if (!point || (!point.y && point.y !== 0)) {
return null;
}
if (this.props.graph === DEFAULT_GRAPH) {

if (graph === DEFAULT_GRAPH) {
return (
<GraphsTooltipsContentIssues
index={idx}
key={serie.name}
measuresHistory={this.props.measuresHistory}
measuresHistory={measuresHistory}
name={serie.name}
tooltipIdx={tooltipIdx}
translatedName={serie.translatedName}
value={this.props.formatValue(point.y)}
/>
);
} else {
return (
<GraphsTooltipsContent
index={idx}
key={serie.name}
name={serie.name}
translatedName={serie.translatedName}
value={this.props.formatValue(point.y)}
/>
);
}

return (
<GraphsTooltipsContent
index={idx}
key={serie.name}
name={serie.name}
translatedName={serie.translatedName}
value={this.props.formatValue(point.y)}
/>
);
});
}

render() {
const { events, measuresHistory, tooltipIdx } = this.props;
const { events, measuresHistory, tooltipIdx, tooltipPos, graph, graphWidth, selectedDate } =
this.props;

const top = 30;
let left = this.props.tooltipPos + 60;
let left = tooltipPos + TOOLTIP_LEFT_MARGIN;
let placement = PopupPlacement.RightTop;
if (left > this.props.graphWidth - TOOLTIP_WIDTH - 50) {
if (left > graphWidth - TOOLTIP_WIDTH - TOOLTIP_LEFT_FLIP_THRESHOLD) {
left -= TOOLTIP_WIDTH;
placement = PopupPlacement.LeftTop;
}

const tooltipContent = this.renderContent().filter(isDefined);
const addSeparator = tooltipContent.length > 0;

return (
<Popup
className="disabled-pointer-events"
@@ -97,21 +104,21 @@ export default class GraphsTooltips extends React.PureComponent<Props> {
>
<div className="activity-graph-tooltip">
<div className="activity-graph-tooltip-title spacer-bottom">
<DateTimeFormatter date={this.props.selectedDate} />
<DateTimeFormatter date={selectedDate} />
</div>
<table className="width-100">
{events && events.length > 0 && (
{events?.length > 0 && (
<GraphsTooltipsContentEvents addSeparator={addSeparator} events={events} />
)}
<tbody>{tooltipContent}</tbody>
{this.props.graph === 'coverage' && (
{graph === GraphType.coverage && (
<GraphsTooltipsContentCoverage
addSeparator={addSeparator}
measuresHistory={measuresHistory}
tooltipIdx={tooltipIdx}
/>
)}
{this.props.graph === 'duplications' && (
{graph === GraphType.duplications && (
<GraphsTooltipsContentDuplication
addSeparator={addSeparator}
measuresHistory={measuresHistory}

+ 5
- 7
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentCoverage.tsx View File

@@ -20,6 +20,7 @@
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';
import { MetricKey } from '../../types/metrics';
import { MeasureHistory } from '../../types/project-activity';

export interface GraphsTooltipsContentCoverageProps {
@@ -28,13 +29,10 @@ export interface GraphsTooltipsContentCoverageProps {
tooltipIdx: number;
}

export default function GraphsTooltipsContentCoverage({
addSeparator,
measuresHistory,
tooltipIdx,
}: GraphsTooltipsContentCoverageProps) {
const uncovered = measuresHistory.find((measure) => measure.metric === 'uncovered_lines');
const coverage = measuresHistory.find((measure) => measure.metric === 'coverage');
export default function GraphsTooltipsContentCoverage(props: GraphsTooltipsContentCoverageProps) {
const { addSeparator, measuresHistory, tooltipIdx } = props;
const uncovered = measuresHistory.find((measure) => measure.metric === MetricKey.uncovered_lines);
const coverage = measuresHistory.find((measure) => measure.metric === MetricKey.coverage);
if (!uncovered || !uncovered.history[tooltipIdx] || !coverage || !coverage.history[tooltipIdx]) {
return null;
}

+ 6
- 6
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentDuplication.tsx View File

@@ -20,6 +20,7 @@
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { formatMeasure } from '../../helpers/measures';
import { MetricKey } from '../../types/metrics';
import { MeasureHistory } from '../../types/project-activity';

export interface GraphsTooltipsContentDuplicationProps {
@@ -28,13 +29,12 @@ export interface GraphsTooltipsContentDuplicationProps {
tooltipIdx: number;
}

export default function GraphsTooltipsContentDuplication({
addSeparator,
measuresHistory,
tooltipIdx,
}: GraphsTooltipsContentDuplicationProps) {
export default function GraphsTooltipsContentDuplication(
props: GraphsTooltipsContentDuplicationProps
) {
const { addSeparator, measuresHistory, tooltipIdx } = props;
const duplicationDensity = measuresHistory.find(
(measure) => measure.metric === 'duplicated_lines_density'
(measure) => measure.metric === MetricKey.duplicated_lines_density
);
if (!duplicationDensity || !duplicationDensity.history[tooltipIdx]) {
return null;

+ 12
- 12
server/sonar-web/src/main/js/components/activity-graph/GraphsTooltipsContentIssues.tsx View File

@@ -20,6 +20,7 @@
import * as React from 'react';
import ChartLegendIcon from '../../components/icons/ChartLegendIcon';
import Rating from '../../components/ui/Rating';
import { MetricKey } from '../../types/metrics';
import { MeasureHistory } from '../../types/project-activity';
import { Dict } from '../../types/types';

@@ -33,29 +34,28 @@ export interface GraphsTooltipsContentIssuesProps {
}

const METRIC_RATING: Dict<string> = {
bugs: 'reliability_rating',
vulnerabilities: 'security_rating',
code_smells: 'sqale_rating',
[MetricKey.bugs]: MetricKey.reliability_rating,
[MetricKey.vulnerabilities]: MetricKey.security_rating,
[MetricKey.code_smells]: MetricKey.sqale_rating,
};

export default function GraphsTooltipsContentIssues(props: GraphsTooltipsContentIssuesProps) {
const rating = props.measuresHistory.find(
(measure) => measure.metric === METRIC_RATING[props.name]
);
if (!rating || !rating.history[props.tooltipIdx]) {
const { index, measuresHistory, name, tooltipIdx, translatedName, value } = props;
const rating = measuresHistory.find((measure) => measure.metric === METRIC_RATING[name]);
if (!rating || !rating.history[tooltipIdx]) {
return null;
}
const ratingValue = rating.history[props.tooltipIdx].value;
const ratingValue = rating.history[tooltipIdx].value;
return (
<tr className="activity-graph-tooltip-issues-line" key={props.name}>
<tr className="activity-graph-tooltip-issues-line" key={name}>
<td className="thin">
<ChartLegendIcon className="spacer-right" index={props.index} />
<ChartLegendIcon className="spacer-right" index={index} />
</td>
<td className="text-right spacer-right">
<span className="activity-graph-tooltip-value">{props.value}</span>
<span className="activity-graph-tooltip-value">{value}</span>
{ratingValue && <Rating className="spacer-left" small={true} value={ratingValue} />}
</td>
<td>{props.translatedName}</td>
<td>{translatedName}</td>
</tr>
);
}

+ 173
- 149
server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx View File

@@ -26,12 +26,7 @@ import * as React from 'react';
import selectEvent from 'react-select-event';
import { byLabelText, byPlaceholderText, byRole, byText } from 'testing-library-selector';
import { parseDate } from '../../../helpers/dates';
import {
mockAnalysisEvent,
mockHistoryItem,
mockMeasureHistory,
mockParsedAnalysis,
} from '../../../helpers/mocks/project-activity';
import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity';
import { mockMetric } from '../../../helpers/testMocks';
import { renderComponent } from '../../../helpers/testReactTestingUtils';
import { MetricKey } from '../../../types/metrics';
@@ -41,63 +36,65 @@ import GraphsHeader from '../GraphsHeader';
import GraphsHistory from '../GraphsHistory';
import { generateSeries, getDisplayedHistoryMetrics, splitSeriesInGraphs } from '../utils';

const ui = {
// Graph types.
graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),

// Add metrics.
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }),
newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }),
hiddenOptionsAlert: byText('project_activity.graphs.custom.type_x_message', {
exact: false,
}),
maxOptionsAlert: byText('project_activity.graphs.custom.add_metric_info'),
filterMetrics: byPlaceholderText('search.search_for_metrics'),

// Graphs.
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }),
noDataText: byText('project_activity.graphs.custom.no_history'),

// Date filters.
fromDateInput: byLabelText('from_date'),
toDateInput: byLabelText('to_date'),
submitDatesBtn: byRole('button', { name: 'Submit dates' }),

// Data in table.
openInTableBtn: byRole('button', { name: 'project_activity.graphs.open_in_table' }),
closeDataTableBtn: byRole('button', { name: 'close' }),
dataTable: byRole('table'),
dataTableRows: byRole('row'),
dataTableColHeaders: byRole('columnheader'),
onlyFirst100Text: byText('project_activity.graphs.data_table.max_lines_warning.100'),
noDataTableText: byText('project_activity.graphs.data_table.no_data_warning_check_dates_x', {
exact: false,
}),
};
const MAX_GRAPHS = 2;
const MAX_SERIES_PER_GRAPH = 3;
const HISTORY_COUNT = 10;
const START_DATE = '2016-01-01T00:00:00+0200';

it('should correctly handle adding/removing custom metrics', async () => {
it('should render correctly when loading', async () => {
renderActivityGraph({ loading: true });
expect(await screen.findByLabelText('loading')).toBeInTheDocument();
});

it('should show the correct legend items', async () => {
const user = userEvent.setup();
const ui = getPageObject(user);
renderActivityGraph();

// Static legend items, which aren't interactive.
expect(ui.legendRemoveMetricBtn(MetricKey.bugs).query()).not.toBeInTheDocument();
expect(ui.getLegendItem(MetricKey.bugs)).toBeInTheDocument();

// Switch to custom graph.
await ui.changeGraphType(GraphType.custom);
await ui.openAddMetrics();
await ui.clickOnMetric(MetricKey.bugs);
await ui.clickOnMetric(MetricKey.test_failures);
await user.keyboard('{Escape}');

// These legend items are interactive (interaction tested below).
expect(ui.legendRemoveMetricBtn(MetricKey.bugs).get()).toBeInTheDocument();
expect(ui.legendRemoveMetricBtn(MetricKey.test_failures).get()).toBeInTheDocument();

// Shows warning for metrics with no data.
const li = ui.getLegendItem(MetricKey.test_failures);
// eslint-disable-next-line jest/no-conditional-in-test
if (li) {
li.focus();
}
expect(ui.noDataWarningTooltip.get()).toBeInTheDocument();
});

it('should correctly handle adding/removing custom metrics', async () => {
const ui = getPageObject(userEvent.setup());
renderActivityGraph();

// Change graph type to "Custom".
await changeGraphType(GraphType.custom);
await ui.changeGraphType(GraphType.custom);

// Open the "Add metrics" dropdown button; select some metrics.
await toggleAddMetrics(user);
await ui.openAddMetrics();

// We should not see DATA type or New Code metrics.
expect(ui.newBugsCheckbox.query()).not.toBeInTheDocument();
expect(ui.burnedBudgetCheckbox.query()).not.toBeInTheDocument();

// Select 3 Int types.
await clickOnMetric(user, MetricKey.bugs);
await clickOnMetric(user, MetricKey.code_smells);
await clickOnMetric(user, MetricKey.confirmed_issues);
await ui.clickOnMetric(MetricKey.bugs);
await ui.clickOnMetric(MetricKey.code_smells);
await ui.clickOnMetric(MetricKey.confirmed_issues);
// Select 1 Percent type.
await clickOnMetric(user, MetricKey.coverage);
await ui.clickOnMetric(MetricKey.coverage);

// We should see 2 graphs, correctly labelled.
expect(ui.graphs.getAll()).toHaveLength(2);
@@ -107,8 +104,8 @@ it('should correctly handle adding/removing custom metrics', async () => {
expect(ui.hiddenOptionsAlert.get()).toBeInTheDocument();

// Select 2 more Percent types.
await clickOnMetric(user, MetricKey.duplicated_lines_density);
await clickOnMetric(user, MetricKey.test_success_density);
await ui.clickOnMetric(MetricKey.duplicated_lines_density);
await ui.clickOnMetric(MetricKey.test_success_density);

// We cannot select anymore options. It should disable all remaining options, and
// show a different alert.
@@ -118,103 +115,139 @@ it('should correctly handle adding/removing custom metrics', async () => {
expect(ui.vulnerabilityCheckbox.get()).toHaveAttribute('aria-disabled', 'true');

// Disable a few options.
await clickOnMetric(user, MetricKey.bugs);
await clickOnMetric(user, MetricKey.code_smells);
await clickOnMetric(user, MetricKey.coverage);
await ui.clickOnMetric(MetricKey.bugs);
await ui.clickOnMetric(MetricKey.code_smells);
await ui.clickOnMetric(MetricKey.coverage);

// Search for option.
await searchForMetric(user, 'bug');
await ui.searchForMetric('bug');
expect(ui.bugsCheckbox.get()).toBeInTheDocument();
expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument();
toggleAddMetrics(user);

// Disable final metrics by clicking on the legend items.
await removeMetric(user, MetricKey.confirmed_issues);
await removeMetric(user, MetricKey.duplicated_lines_density);
await removeMetric(user, MetricKey.test_success_density);
await ui.removeMetric(MetricKey.confirmed_issues);
await ui.removeMetric(MetricKey.duplicated_lines_density);
await ui.removeMetric(MetricKey.test_success_density);

// Should show message that there's no data to be rendered.
expect(ui.noDataText.get()).toBeInTheDocument();
});

it('should render correctly when loading', async () => {
renderActivityGraph({ loading: true });
expect(await screen.findByLabelText('loading')).toBeInTheDocument();
});
describe('data table modal', () => {
it('shows the same data in a table', async () => {
const ui = getPageObject(userEvent.setup());
renderActivityGraph();

it('shows the same data in a table', async () => {
const user = userEvent.setup();
renderActivityGraph();
await ui.openDataTable();
expect(ui.dataTable.get()).toBeInTheDocument();
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5);
expect(ui.dataTableRows.getAll()).toHaveLength(HISTORY_COUNT + 1);

await user.click(ui.openInTableBtn.get());
expect(ui.dataTable.get()).toBeInTheDocument();
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5);
expect(ui.dataTableRows.getAll()).toHaveLength(101);
expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument();
expect(screen.getByText('event.category.VERSION', { exact: false })).toBeInTheDocument();
expect(
screen.getByText('event.category.DEFINITION_CHANGE', { exact: false })
).toBeInTheDocument();
expect(ui.onlyFirst100Text.get()).toBeInTheDocument();

// Change graph type and dates, check table updates correctly.
await user.click(ui.closeDataTableBtn.get());
await changeGraphType(GraphType.coverage);

await user.click(ui.openInTableBtn.get());
expect(ui.dataTable.get()).toBeInTheDocument();
expect(ui.dataTableColHeaders.getAll()).toHaveLength(4);
expect(ui.dataTableRows.getAll()).toHaveLength(101);
});
// Change graph type and dates, check table updates correctly.
await ui.closeDataTable();
await ui.changeGraphType(GraphType.coverage);

it('shows the same data in a table when filtered by date', async () => {
const user = userEvent.setup();
renderActivityGraph({
graphStartDate: parseDate('2017-01-01'),
graphEndDate: parseDate('2019-01-01'),
await ui.openDataTable();
expect(ui.dataTable.get()).toBeInTheDocument();
expect(ui.dataTableColHeaders.getAll()).toHaveLength(4);
expect(ui.dataTableRows.getAll()).toHaveLength(HISTORY_COUNT + 1);
});

await user.click(ui.openInTableBtn.get());
expect(ui.dataTable.get()).toBeInTheDocument();
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5);
expect(ui.dataTableRows.getAll()).toHaveLength(2);
expect(ui.onlyFirst100Text.query()).not.toBeInTheDocument();
});

async function changeGraphType(type: GraphType) {
await selectEvent.select(ui.graphTypeSelect.get(), [`project_activity.graphs.${type}`]);
}

async function toggleAddMetrics(user: UserEvent) {
await user.click(ui.addMetricBtn.get());
}

async function clickOnMetric(user: UserEvent, name: MetricKey) {
await user.click(screen.getByRole('checkbox', { name }));
}
it('shows the same data in a table when filtered by date', async () => {
const ui = getPageObject(userEvent.setup());
renderActivityGraph({
graphStartDate: parseDate('2017-01-01'),
graphEndDate: parseDate('2019-01-01'),
});

async function searchForMetric(user: UserEvent, text: string) {
await user.type(ui.filterMetrics.get(), text);
}
await ui.openDataTable();
expect(ui.dataTable.get()).toBeInTheDocument();
expect(ui.dataTableColHeaders.getAll()).toHaveLength(5);
expect(ui.dataTableRows.getAll()).toHaveLength(2);
});
});

async function removeMetric(user: UserEvent, metric: MetricKey) {
await user.click(
screen.getByRole('button', { name: `project_activity.graphs.custom.remove_metric.${metric}` })
);
function getPageObject(user: UserEvent) {
const ui = {
// Graph types.
graphTypeSelect: byLabelText('project_activity.graphs.choose_type'),

// Add/remove metrics.
addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
bugsCheckbox: byRole('checkbox', { name: MetricKey.bugs }),
newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }),
hiddenOptionsAlert: byText('project_activity.graphs.custom.type_x_message', {
exact: false,
}),
maxOptionsAlert: byText('project_activity.graphs.custom.add_metric_info'),
filterMetrics: byPlaceholderText('search.search_for_metrics'),
legendRemoveMetricBtn: (key: string) =>
byRole('button', { name: `project_activity.graphs.custom.remove_metric.${key}` }),
getLegendItem: (name: string) => {
// This is due to a limitation in testing library, where we cannot get a listitem
// role element by name.
return screen.getAllByRole('listitem').find((item) => item.textContent === name);
},
noDataWarningTooltip: byLabelText('project_activity.graphs.custom.metric_no_history'),

// Graphs.
graphs: byLabelText('project_activity.graphs.explanation_x', { exact: false }),
noDataText: byText('project_activity.graphs.custom.no_history'),

// Date filters.
fromDateInput: byLabelText('from_date'),
toDateInput: byLabelText('to_date'),
submitDatesBtn: byRole('button', { name: 'Submit dates' }),

// Data in table.
openInTableBtn: byRole('button', { name: 'project_activity.graphs.open_in_table' }),
closeDataTableBtn: byRole('button', { name: 'close' }),
dataTable: byRole('table'),
dataTableRows: byRole('row'),
dataTableColHeaders: byRole('columnheader'),
noDataTableText: byText('project_activity.graphs.data_table.no_data_warning_check_dates_x', {
exact: false,
}),
};

return {
...ui,
async changeGraphType(type: GraphType) {
await selectEvent.select(ui.graphTypeSelect.get(), [`project_activity.graphs.${type}`]);
},
async openAddMetrics() {
await user.click(ui.addMetricBtn.get());
},
async searchForMetric(text: string) {
await user.type(ui.filterMetrics.get(), text);
},
async clickOnMetric(name: MetricKey) {
await user.click(screen.getByRole('checkbox', { name }));
},
async removeMetric(metric: MetricKey) {
await user.click(ui.legendRemoveMetricBtn(metric).get());
},
async openDataTable() {
await user.click(ui.openInTableBtn.get());
},
async closeDataTable() {
await user.click(ui.closeDataTableBtn.get());
},
};
}

function renderActivityGraph(
graphsHistoryProps: Partial<GraphsHistory['props']> = {},
graphsHeaderProps: Partial<GraphsHeader['props']> = {}
) {
const MAX_GRAPHS = 2;
const MAX_SERIES_PER_GRAPH = 3;
const HISTORY_COUNT = 100;

function ActivityGraph() {
const [selectedMetrics, setSelectedMetrics] = React.useState<string[]>([]);
const [graph, setGraph] = React.useState(GraphType.issues);
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(undefined);
const [graph, setGraph] = React.useState(graphsHistoryProps.graph || GraphType.issues);
const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(
graphsHistoryProps.selectedDate
);
const [fromDate, setFromDate] = React.useState<Date | undefined>(undefined);
const [toDate, setToDate] = React.useState<Date | undefined>(undefined);

@@ -232,8 +265,8 @@ function renderActivityGraph(
MetricKey.duplicated_lines_density,
MetricKey.test_success_density,
].forEach((metric) => {
const history = times(HISTORY_COUNT, (i) => {
const date = parseDate('2016-01-01T00:00:00+0200');
const history = times(HISTORY_COUNT - 2, (i) => {
const date = parseDate(START_DATE);
date.setDate(date.getDate() + i);
return mockHistoryItem({ date, value: i.toString() });
});
@@ -245,7 +278,6 @@ function renderActivityGraph(
metrics.push(
mockMetric({
key: metric,
name: metric,
type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT',
})
);
@@ -253,8 +285,21 @@ function renderActivityGraph(

// The following should be filtered out, and not be suggested as options.
metrics.push(
mockMetric({ key: MetricKey.new_bugs, name: MetricKey.new_bugs, type: 'INT' }),
mockMetric({ key: MetricKey.burned_budget, name: MetricKey.burned_budget, type: 'DATA' })
mockMetric({ key: MetricKey.new_bugs, type: 'INT' }),
mockMetric({ key: MetricKey.burned_budget, type: 'DATA' })
);

// The following will not be filtered out, but has no values.
metrics.push(mockMetric({ key: MetricKey.test_failures, type: 'INT' }));
measuresHistory.push(
mockMeasureHistory({
metric: MetricKey.test_failures,
history: times(HISTORY_COUNT, (i) => {
const date = parseDate(START_DATE);
date.setDate(date.getDate() + i);
return mockHistoryItem({ date, value: undefined });
}),
})
);

const series = generateSeries(
@@ -305,28 +350,7 @@ function renderActivityGraph(
{...graphsHeaderProps}
/>
<GraphsHistory
analyses={[
mockParsedAnalysis({
date: parseDate('2018-10-27T12:21:15+0200'),
events: [
mockAnalysisEvent({ key: '1' }),
mockAnalysisEvent({
key: '2',
category: 'VERSION',
description: undefined,
qualityGate: undefined,
}),
mockAnalysisEvent({
key: '3',
category: 'DEFINITION_CHANGE',
definitionChange: {
projects: [{ changeType: 'ADDED', key: 'foo', name: 'Foo' }],
},
qualityGate: undefined,
}),
],
}),
]}
analyses={[]}
graph={graph}
graphEndDate={toDate}
graphStartDate={fromDate}

server/sonar-web/src/main/js/components/activity-graph/__tests__/DataTableModal-test.tsx → server/sonar-web/src/main/js/components/activity-graph/__tests__/DataTableModal-it.tsx View File

@@ -22,13 +22,18 @@ import { screen } from '@testing-library/react';
import { times } from 'lodash';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity';
import {
mockAnalysisEvent,
mockHistoryItem,
mockMeasureHistory,
mockParsedAnalysis,
} from '../../../helpers/mocks/project-activity';
import { mockMetric } from '../../../helpers/testMocks';
import { renderComponent } from '../../../helpers/testReactTestingUtils';
import { MetricKey } from '../../../types/metrics';
import { GraphType, MeasureHistory } from '../../../types/project-activity';
import { Metric } from '../../../types/types';
import DataTableModal, { DataTableModalProps } from '../DataTableModal';
import DataTableModal, { DataTableModalProps, MAX_DATA_TABLE_ROWS } from '../DataTableModal';
import { generateSeries, getDisplayedHistoryMetrics } from '../utils';

it('should render correctly if there are no series', () => {
@@ -38,10 +43,22 @@ it('should render correctly if there are no series', () => {
).toBeInTheDocument();
});

it('should render correctly if there are events', () => {
renderDataTableModal({
analyses: [
mockParsedAnalysis({
date: parseDate('2016-01-01T00:00:00+0200'),
events: [mockAnalysisEvent({ key: '1', category: 'QUALITY_GATE' })],
}),
],
});
expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument();
});

it('should render correctly if there is too much data', () => {
renderDataTableModal({ series: mockSeries(101) });
renderDataTableModal({ series: mockSeries(MAX_DATA_TABLE_ROWS + 1) });
expect(
screen.getByText('project_activity.graphs.data_table.max_lines_warning.100')
screen.getByText(`project_activity.graphs.data_table.max_lines_warning.${MAX_DATA_TABLE_ROWS}`)
).toBeInTheDocument();
});


+ 0
- 88
server/sonar-web/src/main/js/components/activity-graph/__tests__/DefinitionChangeEventInner-test.tsx View File

@@ -1,88 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockBranch } from '../../../helpers/mocks/branch-like';
import { click } from '../../../helpers/testUtils';
import { DefinitionChangeEvent, DefinitionChangeEventInner } from '../DefinitionChangeEventInner';

it('should render', () => {
const event: DefinitionChangeEvent = {
category: 'DEFINITION_CHANGE',
key: 'foo1234',
name: '',
definitionChange: {
projects: [
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master' },
{ changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master' },
],
},
};
const wrapper = shallow(<DefinitionChangeEventInner branchLike={undefined} event={event} />);
expect(wrapper).toMatchSnapshot();

click(wrapper.find('.project-activity-event-inner-more-link'));
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('should render for a branch', () => {
const branch = mockBranch({ name: 'feature-x' });
const event: DefinitionChangeEvent = {
category: 'DEFINITION_CHANGE',
key: 'foo1234',
name: '',
definitionChange: {
projects: [
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'feature-x' },
{
changeType: 'BRANCH_CHANGED',
key: 'bar',
name: 'Bar',
oldBranch: 'master',
newBranch: 'feature-y',
},
],
},
};
const wrapper = shallow(<DefinitionChangeEventInner branchLike={branch} event={event} />);
click(wrapper.find('.project-activity-event-inner-more-link'));
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('should render when readonly', () => {
const event: DefinitionChangeEvent = {
category: 'DEFINITION_CHANGE',
key: 'foo1234',
name: '',
definitionChange: {
projects: [
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master' },
{ changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master' },
],
},
};
const wrapper = shallow(
<DefinitionChangeEventInner branchLike={undefined} event={event} readonly={true} />
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('.project-activity-event-inner-more-link').exists()).toBe(false);
});

+ 165
- 0
server/sonar-web/src/main/js/components/activity-graph/__tests__/EventInner-it.tsx View File

@@ -0,0 +1,165 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { byRole, byText } from 'testing-library-selector';
import { mockAnalysisEvent } from '../../../helpers/mocks/project-activity';
import { renderComponent } from '../../../helpers/testReactTestingUtils';
import EventInner, { EventInnerProps } from '../EventInner';

const ui = {
showMoreBtn: byRole('button', { name: 'more' }),
showLessBtn: byRole('button', { name: 'hide' }),
projectLink: (name: string) => byRole('link', { name }),

definitionChangeLabel: byText('event.category.DEFINITION_CHANGE', { exact: false }),
projectAddedTxt: byText('event.definition_change.added'),
projectRemovedTxt: byText('event.definition_change.removed'),
branchReplacedTxt: byText('event.definition_change.branch_replaced'),

qualityGateLabel: byText('event.category.QUALITY_GATE', { exact: false }),
stillFailingTxt: byText('event.quality_gate.still_x'),

versionLabel: byText('event.category.VERSION', { exact: false }),
};

describe('DEFINITION_CHANGE events', () => {
it('should render correctly for "DEFINITION_CHANGE" events', async () => {
const user = userEvent.setup();
renderEventInner({
event: mockAnalysisEvent({
category: 'DEFINITION_CHANGE',
definitionChange: {
projects: [
{ changeType: 'ADDED', key: 'foo', name: 'Foo', branch: 'master-foo' },
{ changeType: 'REMOVED', key: 'bar', name: 'Bar', branch: 'master-bar' },
{
changeType: 'BRANCH_CHANGED',
key: 'baz',
name: 'Baz',
oldBranch: 'old-branch',
newBranch: 'new-branch',
},
],
},
}),
});

expect(ui.definitionChangeLabel.get()).toBeInTheDocument();

await user.click(ui.showMoreBtn.get());

// ADDED.
expect(ui.projectAddedTxt.get()).toBeInTheDocument();
expect(ui.projectLink('Foo').get()).toBeInTheDocument();
expect(screen.getByText('master-foo')).toBeInTheDocument();

// REMOVED.
expect(ui.projectRemovedTxt.get()).toBeInTheDocument();
expect(ui.projectLink('Bar').get()).toBeInTheDocument();
expect(screen.getByText('master-bar')).toBeInTheDocument();

// BRANCH_CHANGED
expect(ui.branchReplacedTxt.get()).toBeInTheDocument();
expect(ui.projectLink('Baz').get()).toBeInTheDocument();
expect(screen.getByText('old-branch')).toBeInTheDocument();
expect(screen.getByText('new-branch')).toBeInTheDocument();
});
});

describe('QUALITY_GATE events', () => {
it('should render correctly for simple "QUALITY_GATE" events', () => {
renderEventInner({
event: mockAnalysisEvent({
category: 'QUALITY_GATE',
qualityGate: { status: 'ERROR', stillFailing: false, failing: [] },
}),
});

expect(ui.qualityGateLabel.get()).toBeInTheDocument();
});

it('should render correctly for "still failing" "QUALITY_GATE" events', () => {
renderEventInner({
event: mockAnalysisEvent({
category: 'QUALITY_GATE',
qualityGate: { status: 'ERROR', stillFailing: true, failing: [] },
}),
});

expect(ui.qualityGateLabel.get()).toBeInTheDocument();
expect(ui.stillFailingTxt.get()).toBeInTheDocument();
});

it('should render correctly for application "QUALITY_GATE" events', async () => {
const user = userEvent.setup();
renderEventInner({
event: mockAnalysisEvent({
category: 'QUALITY_GATE',
qualityGate: {
status: 'ERROR',
stillFailing: true,
failing: [
{
key: 'foo',
name: 'Foo',
branch: 'master',
},
{
key: 'bar',
name: 'Bar',
branch: 'feature/bar',
},
],
},
}),
});

expect(ui.qualityGateLabel.get()).toBeInTheDocument();

await user.click(ui.showMoreBtn.get());
expect(ui.projectLink('Foo').get()).toBeInTheDocument();
expect(ui.projectLink('Bar').get()).toBeInTheDocument();

await user.click(ui.showLessBtn.get());
expect(ui.projectLink('Foo').query()).not.toBeInTheDocument();
expect(ui.projectLink('Bar').query()).not.toBeInTheDocument();
});
});

describe('VERSION events', () => {
it('should render correctly', () => {
renderEventInner({
event: mockAnalysisEvent({
category: 'VERSION',
name: '1.0',
}),
});

expect(ui.versionLabel.get()).toBeInTheDocument();
expect(screen.getByText('1.0')).toBeInTheDocument();
});
});

function renderEventInner(props: Partial<EventInnerProps> = {}) {
return renderComponent(<EventInner event={mockAnalysisEvent()} {...props} />);
}

+ 0
- 56
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendCustom-test.tsx View File

@@ -1,56 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import GraphsLegendCustom, { GraphsLegendCustomProps } from '../GraphsLegendCustom';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
});

function shallowRender(props: Partial<GraphsLegendCustomProps> = {}) {
return shallow<GraphsLegendCustomProps>(
<GraphsLegendCustom
removeMetric={jest.fn()}
series={[
{
name: 'bugs',
translatedName: 'Bugs',
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }],
type: 'INT',
},
{
name: 'my_metric',
translatedName: 'My Metric',
data: [{ x: parseDate('2017-05-16T13:50:02+0200'), y: 1 }],
type: 'INT',
},
{
name: 'foo',
translatedName: 'Foo',
data: [],
type: 'INT',
},
]}
{...props}
/>
);
}

+ 0
- 51
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendItem-test.tsx View File

@@ -1,51 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { ClearButton } from '../../../components/controls/buttons';
import { click } from '../../../helpers/testUtils';
import GraphsLegendItem from '../GraphsLegendItem';

it('should render correctly a legend', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(
shallowRender({
className: 'myclass',
index: 1,
metric: 'foo',
name: 'Foo',
removeMetric: jest.fn(),
})
).toMatchSnapshot('with legend');
expect(shallowRender({ showWarning: true })).toMatchSnapshot('with warning');
});

it('should correctly handle clicks', () => {
const removeMetric = jest.fn();
const wrapper = shallowRender({ removeMetric });
click(wrapper.find(ClearButton));
expect(removeMetric).toHaveBeenCalledWith('bugs');
});

function shallowRender(props: Partial<GraphsLegendItem['props']> = {}) {
return shallow<GraphsLegendItem>(
<GraphsLegendItem index={2} metric="bugs" name="Bugs" {...props} />
);
}

+ 0
- 30
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendNewCode-test.tsx View File

@@ -1,30 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import GraphsLegendNewCode from '../GraphsLegendNewCode';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
});

function shallowRender() {
return shallow(<GraphsLegendNewCode />);
}

+ 0
- 38
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsLegendStatic-test.tsx View File

@@ -1,38 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import GraphsLegendStatic, { GraphsLegendStaticProps } from '../GraphsLegendStatic';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
});

function shallowRender(props: Partial<GraphsLegendStaticProps> = {}) {
return shallow<GraphsLegendStaticProps>(
<GraphsLegendStatic
series={[
{ name: 'bugs', translatedName: 'Bugs' },
{ name: 'code_smells', translatedName: 'Code Smells' },
]}
{...props}
/>
);
}

+ 127
- 0
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-it.tsx View File

@@ -0,0 +1,127 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { screen } from '@testing-library/react';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import {
mockAnalysisEvent,
mockHistoryItem,
mockMeasureHistory,
} from '../../../helpers/mocks/project-activity';
import { mockMetric } from '../../../helpers/testMocks';
import { renderComponent } from '../../../helpers/testReactTestingUtils';
import { MetricKey } from '../../../types/metrics';
import { GraphType, MeasureHistory } from '../../../types/project-activity';
import { Metric } from '../../../types/types';
import GraphsTooltips from '../GraphsTooltips';
import { generateSeries, getDisplayedHistoryMetrics } from '../utils';

it.each([
[
GraphType.issues,
[
[MetricKey.bugs, 1, 'C'],
[MetricKey.code_smells, 0, 'A'],
[MetricKey.vulnerabilities, 2, 'E'],
],
],
[
GraphType.coverage,
[
['metric.coverage.name', '75.0%'],
['metric.uncovered_lines.name', 8],
],
],
[GraphType.duplications, [['metric.duplicated_lines_density.name', '3.0%']]],
[GraphType.custom, [[MetricKey.bugs, 1]]],
])(
'renders correctly for graph of type %s',
(graph, metrics: Array<[string, number, string] | [string, number]>) => {
renderGraphsTooltips({ graph });

// Render events.
expect(screen.getByText('event.category.QUALITY_GATE', { exact: false })).toBeInTheDocument();

// Measures table.
metrics.forEach(([key, n, rating]) => {
expect(
screen.getByRole('row', {
// eslint-disable-next-line jest/no-conditional-in-test
name: rating ? `${n} metric.has_rating_X.${rating} ${key}` : `${n} ${key}`,
})
).toBeInTheDocument();
});
}
);

function renderGraphsTooltips(props: Partial<GraphsTooltips['props']> = {}) {
const graph = (props.graph as GraphType) || GraphType.coverage;
const measuresHistory: MeasureHistory[] = [];
const date = props.selectedDate || parseDate('2016-01-01T00:00:00+0200');
const metrics: Metric[] = [];

[
[MetricKey.bugs, '1'],
[MetricKey.reliability_rating, '3'],
[MetricKey.code_smells, '0'],
[MetricKey.sqale_rating, '1'],
[MetricKey.vulnerabilities, '2'],
[MetricKey.security_rating, '5'],
[MetricKey.lines_to_cover, '10'],
[MetricKey.uncovered_lines, '8'],
[MetricKey.coverage, '75'],
[MetricKey.duplicated_lines_density, '3'],
].forEach(([metric, value]) => {
measuresHistory.push(
mockMeasureHistory({
metric,
history: [mockHistoryItem({ date, value })],
})
);
metrics.push(
mockMetric({
key: metric,
type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT',
})
);
});

const series = generateSeries(
measuresHistory,
graph,
metrics,
getDisplayedHistoryMetrics(graph, graph === GraphType.custom ? [MetricKey.bugs] : [])
);

return renderComponent(
<GraphsTooltips
events={[mockAnalysisEvent({ key: '1' })]}
graph={graph}
graphWidth={100}
measuresHistory={measuresHistory}
selectedDate={date}
series={series}
tooltipIdx={0}
tooltipPos={0}
formatValue={(n: number | string) => String(n)}
{...props}
/>
);
}

+ 0
- 111
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltips-test.tsx View File

@@ -1,111 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import { mockEvent } from '../../../helpers/testUtils';
import GraphsTooltips from '../GraphsTooltips';
import { DEFAULT_GRAPH } from '../utils';

const SERIES_ISSUES = [
{
name: 'bugs',
translatedName: 'Bugs',
data: [
{
x: parseDate('2011-10-01T22:01:00.000Z'),
y: 3,
},
{
x: parseDate('2011-10-25T10:27:41.000Z'),
y: 0,
},
],
type: 'INT',
},
{
name: 'code_smells',
translatedName: 'Code Smells',
data: [
{
x: parseDate('2011-10-01T22:01:00.000Z'),
y: 18,
},
{
x: parseDate('2011-10-25T10:27:41.000Z'),
y: 15,
},
],
type: 'INT',
},
{
name: 'vulnerabilities',
translatedName: 'Vulnerabilities',
data: [
{
x: parseDate('2011-10-01T22:01:00.000Z'),
y: 0,
},
{
x: parseDate('2011-10-25T10:27:41.000Z'),
y: 1,
},
],
type: 'INT',
},
];

const DEFAULT_PROPS: GraphsTooltips['props'] = {
events: [],
formatValue: (val) => 'Formated.' + val,
graph: DEFAULT_GRAPH,
graphWidth: 500,
measuresHistory: [],
selectedDate: parseDate('2011-10-01T22:01:00.000Z'),
series: SERIES_ISSUES,
tooltipIdx: 0,
tooltipPos: 666,
};

it('should render correctly for issues graphs', () => {
expect(shallow(<GraphsTooltips {...DEFAULT_PROPS} />)).toMatchSnapshot();
expect(shallow(<GraphsTooltips {...DEFAULT_PROPS} events={[mockEvent()]} />)).toMatchSnapshot(
'with events'
);
});

it('should render correctly for random graphs', () => {
expect(
shallow(
<GraphsTooltips
{...DEFAULT_PROPS}
graph="random"
selectedDate={parseDate('2011-10-25T10:27:41.000Z')}
tooltipIdx={1}
/>
)
).toMatchSnapshot();
});

it('should not add separators if not needed', () => {
expect(
shallow(<GraphsTooltips {...DEFAULT_PROPS} graph="coverage" series={[]} />)
).toMatchSnapshot();
});

+ 0
- 33
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContent-test.tsx View File

@@ -1,33 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import GraphsTooltipsContent from '../GraphsTooltipsContent';

const DEFAULT_PROPS = {
index: 1,
name: 'code_smells',
translatedName: 'Code Smells',
value: '1.2k',
};

it('should render correctly', () => {
expect(shallow(<GraphsTooltipsContent {...DEFAULT_PROPS} />)).toMatchSnapshot();
});

+ 0
- 64
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentCoverage-test.tsx View File

@@ -1,64 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import GraphsTooltipsContentCoverage, {
GraphsTooltipsContentCoverageProps,
} from '../GraphsTooltipsContentCoverage';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator');
expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull();
});

function shallowRender(props: Partial<GraphsTooltipsContentCoverageProps> = {}) {
return shallow<GraphsTooltipsContentCoverageProps>(
<GraphsTooltipsContentCoverage
addSeparator={false}
measuresHistory={[
{
metric: 'coverage',
history: [
{ date: parseDate('2011-10-01T22:01:00.000Z') },
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '80.3' },
],
},
{
metric: 'lines_to_cover',
history: [
{ date: parseDate('2011-10-01T22:01:00.000Z'), value: '60545' },
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '65215' },
],
},
{
metric: 'uncovered_lines',
history: [
{ date: parseDate('2011-10-01T22:01:00.000Z'), value: '40564' },
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' },
],
},
]}
tooltipIdx={1}
{...props}
/>
);
}

+ 0
- 51
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentDuplication-test.tsx View File

@@ -1,51 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import GraphsTooltipsContentDuplication, {
GraphsTooltipsContentDuplicationProps,
} from '../GraphsTooltipsContentDuplication';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ addSeparator: true })).toMatchSnapshot('with separator');
expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull();
expect(shallowRender({ measuresHistory: [] }).type()).toBeNull();
});

function shallowRender(props: Partial<GraphsTooltipsContentDuplicationProps> = {}) {
return shallow<GraphsTooltipsContentDuplicationProps>(
<GraphsTooltipsContentDuplication
addSeparator={false}
measuresHistory={[
{
metric: 'duplicated_lines_density',
history: [
{ date: parseDate('2011-10-01T22:01:00.000Z') },
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '10245' },
],
},
]}
tooltipIdx={1}
{...props}
/>
);
}

+ 0
- 33
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentEvents-test.tsx View File

@@ -1,33 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import GraphsTooltipsContentEvents from '../GraphsTooltipsContentEvents';

const EVENTS = [
{ key: '1', category: 'VERSION', name: '6.5' },
{ key: '2', category: 'OTHER', name: 'Foo' },
];

it('should render correctly', () => {
expect(
shallow(<GraphsTooltipsContentEvents addSeparator={true} events={EVENTS} />)
).toMatchSnapshot();
});

+ 0
- 59
server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsTooltipsContentIssues-test.tsx View File

@@ -1,59 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import GraphsTooltipsContentIssues, {
GraphsTooltipsContentIssuesProps,
} from '../GraphsTooltipsContentIssues';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ tooltipIdx: -1 }).type()).toBeNull();
});

function shallowRender(props: Partial<GraphsTooltipsContentIssuesProps> = {}) {
return shallow<GraphsTooltipsContentIssuesProps>(
<GraphsTooltipsContentIssues
index={2}
measuresHistory={[
{
metric: 'bugs',
history: [
{ date: parseDate('2011-10-01T22:01:00.000Z'), value: '500' },
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '1.2k' },
],
},
{
metric: 'reliability_rating',
history: [
{ date: parseDate('2011-10-01T22:01:00.000Z') },
{ date: parseDate('2011-10-25T10:27:41.000Z'), value: '5.0' },
],
},
]}
name="bugs"
tooltipIdx={1}
translatedName="Bugs"
value="1.2k"
{...props}
/>
);
}

+ 0
- 60
server/sonar-web/src/main/js/components/activity-graph/__tests__/RichQualityGateEventInner-test.tsx View File

@@ -1,60 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../../helpers/testUtils';
import { RichQualityGateEvent, RichQualityGateEventInner } from '../RichQualityGateEventInner';

const event: RichQualityGateEvent = {
category: 'QUALITY_GATE',
key: 'foo1234',
name: '',
qualityGate: {
failing: [
{ branch: 'master', key: 'foo', name: 'Foo' },
{ branch: 'master', key: 'bar', name: 'Bar' },
],
status: 'ERROR',
stillFailing: true,
},
};

it('should render', () => {
const wrapper = shallow(<RichQualityGateEventInner event={event} />);
expect(wrapper).toMatchSnapshot();

click(wrapper.find('.project-activity-event-inner-more-link'));
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('should not expand', () => {
const wrapper = shallow(
<RichQualityGateEventInner
event={{ ...event, qualityGate: { ...event.qualityGate, failing: [] } }}
/>
);
expect(wrapper.find('.project-activity-event-inner-more-link').exists()).toBe(false);
});

it('should not expand when readonly', () => {
const wrapper = shallow(<RichQualityGateEventInner event={event} readonly={true} />);
expect(wrapper.find('.project-activity-event-inner-more-link').exists()).toBe(false);
});

+ 0
- 248
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/DefinitionChangeEventInner-test.tsx.snap View File

@@ -1,248 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render 1`] = `
<Fragment>
<span
className="note"
>
event.category.DEFINITION_CHANGE
:
</span>
<div>
<ButtonLink
className="project-activity-event-inner-more-link"
onClick={[Function]}
stopPropagation={true}
>
more
<DropdownIcon
className="little-spacer-left"
turned={false}
/>
</ButtonLink>
</div>
</Fragment>
`;

exports[`should render 2`] = `
<Fragment>
<span
className="note"
>
event.category.DEFINITION_CHANGE
:
</span>
<div>
<ButtonLink
className="project-activity-event-inner-more-link"
onClick={[Function]}
stopPropagation={true}
>
hide
<DropdownIcon
className="little-spacer-left"
turned={true}
/>
</ButtonLink>
</div>
<ul
className="spacer-left spacer-top"
>
<li
className="display-flex-center spacer-top"
key="foo"
>
<div
className="text-ellipsis"
>
<FormattedMessage
defaultMessage="event.definition_change.added"
id="event.definition_change.added"
values={
Object {
"branch": <span
className="nowrap"
title="master"
>
<BranchIcon
className="little-spacer-left text-text-top"
/>
master
</span>,
"project": <ForwardRef(Link)
onClick={[Function]}
title="Foo"
to={
Object {
"pathname": "/dashboard",
"search": "?id=foo&branch=master",
}
}
>
Foo
</ForwardRef(Link)>,
}
}
/>
</div>
</li>
<li
className="display-flex-center spacer-top"
key="bar"
>
<div
className="text-ellipsis"
>
<FormattedMessage
defaultMessage="event.definition_change.removed"
id="event.definition_change.removed"
values={
Object {
"branch": <span
className="nowrap"
title="master"
>
<BranchIcon
className="little-spacer-left text-text-top"
/>
master
</span>,
"project": <ForwardRef(Link)
onClick={[Function]}
title="Bar"
to={
Object {
"pathname": "/dashboard",
"search": "?id=bar&branch=master",
}
}
>
Bar
</ForwardRef(Link)>,
}
}
/>
</div>
</li>
</ul>
</Fragment>
`;

exports[`should render for a branch 1`] = `
<Fragment>
<span
className="note"
>
event.category.DEFINITION_CHANGE
:
</span>
<div>
<ButtonLink
className="project-activity-event-inner-more-link"
onClick={[Function]}
stopPropagation={true}
>
hide
<DropdownIcon
className="little-spacer-left"
turned={true}
/>
</ButtonLink>
</div>
<ul
className="spacer-left spacer-top"
>
<li
className="display-flex-center spacer-top"
key="foo"
>
<div
className="text-ellipsis"
>
<FormattedMessage
defaultMessage="event.definition_change.branch_added"
id="event.definition_change.branch_added"
values={
Object {
"branch": <span
className="nowrap"
title="feature-x"
>
<BranchIcon
className="little-spacer-left text-text-top"
/>
feature-x
</span>,
"project": <ForwardRef(Link)
onClick={[Function]}
title="Foo"
to={
Object {
"pathname": "/dashboard",
"search": "?id=foo&branch=feature-x",
}
}
>
Foo
</ForwardRef(Link)>,
}
}
/>
</div>
</li>
<li
className="display-flex-center spacer-top"
key="bar"
>
<FormattedMessage
defaultMessage="event.definition_change.branch_replaced"
id="event.definition_change.branch_replaced"
values={
Object {
"newBranch": <span
className="nowrap"
title="feature-y"
>
<BranchIcon
className="little-spacer-left text-text-top"
/>
feature-y
</span>,
"oldBranch": <span
className="nowrap"
title="master"
>
<BranchIcon
className="little-spacer-left text-text-top"
/>
master
</span>,
"project": <ForwardRef(Link)
onClick={[Function]}
title="Bar"
to={
Object {
"pathname": "/dashboard",
"search": "?id=bar&branch=feature-y",
}
}
>
Bar
</ForwardRef(Link)>,
}
}
/>
</li>
</ul>
</Fragment>
`;

exports[`should render when readonly 1`] = `
<Fragment>
<span
className="note"
>
event.category.DEFINITION_CHANGE
</span>
</Fragment>
`;

+ 0
- 52
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendCustom-test.tsx.snap View File

@@ -1,52 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<div
className="activity-graph-legends display-flex-center"
>
<div
className="flex-1"
>
<span
className="spacer-left spacer-right"
key="bugs"
>
<GraphsLegendItem
index={0}
metric="bugs"
name="Bugs"
removeMetric={[MockFunction]}
showWarning={false}
/>
</span>
<span
className="spacer-left spacer-right"
key="my_metric"
>
<GraphsLegendItem
index={1}
metric="my_metric"
name="My Metric"
removeMetric={[MockFunction]}
showWarning={false}
/>
</span>
<Tooltip
key="foo"
overlay="project_activity.graphs.custom.metric_no_history"
>
<span
className="spacer-left spacer-right"
>
<GraphsLegendItem
index={2}
metric="foo"
name="Foo"
removeMetric={[MockFunction]}
showWarning={true}
/>
</span>
</Tooltip>
</div>
</div>
`;

+ 0
- 58
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendItem-test.tsx.snap View File

@@ -1,58 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly a legend: default 1`] = `
<span
className=""
>
<ChartLegendIcon
className="text-middle spacer-right"
index={2}
/>
<span
className="text-middle"
>
Bugs
</span>
</span>
`;

exports[`should render correctly a legend: with legend 1`] = `
<span
className="activity-graph-legend-actionable myclass"
>
<ChartLegendIcon
className="text-middle spacer-right"
index={1}
/>
<span
className="text-middle"
>
Foo
</span>
<ClearButton
aria-label="project_activity.graphs.custom.remove_metric.Foo"
className="button-tiny spacer-left text-middle"
iconProps={
Object {
"size": 12,
}
}
onClick={[Function]}
/>
</span>
`;

exports[`should render correctly a legend: with warning 1`] = `
<span
className=""
>
<AlertWarnIcon
className="spacer-right"
/>
<span
className="text-middle"
>
Bugs
</span>
</span>
`;

+ 0
- 14
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendNewCode-test.tsx.snap View File

@@ -1,14 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<Tooltip
overlay="project_activity.graphs.new_code_long"
>
<span
aria-label="project_activity.graphs.new_code_long"
className="activity-graph-new-code-legend display-flex-center pull-right note"
>
project_activity.graphs.new_code
</span>
</Tooltip>
`;

+ 0
- 22
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsLegendStatic-test.tsx.snap View File

@@ -1,22 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<div
className="activity-graph-legends"
>
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
index={0}
key="bugs"
metric="bugs"
name="Bugs"
/>
<GraphsLegendItem
className="big-spacer-left big-spacer-right"
index={1}
key="code_smells"
metric="code_smells"
name="Code Smells"
/>
</div>
`;

+ 0
- 226
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltips-test.tsx.snap View File

@@ -1,226 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should not add separators if not needed 1`] = `
<Popup
className="disabled-pointer-events"
placement="left-top"
style={
Object {
"left": 476,
"top": 30,
"width": 250,
}
}
>
<div
className="activity-graph-tooltip"
>
<div
className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-01T22:01:00.000Z}
/>
</div>
<table
className="width-100"
>
<tbody />
<GraphsTooltipsContentCoverage
addSeparator={false}
measuresHistory={Array []}
tooltipIdx={0}
/>
</table>
</div>
</Popup>
`;

exports[`should render correctly for issues graphs 1`] = `
<Popup
className="disabled-pointer-events"
placement="left-top"
style={
Object {
"left": 476,
"top": 30,
"width": 250,
}
}
>
<div
className="activity-graph-tooltip"
>
<div
className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-01T22:01:00.000Z}
/>
</div>
<table
className="width-100"
>
<tbody>
<GraphsTooltipsContentIssues
index={0}
key="bugs"
measuresHistory={Array []}
name="bugs"
tooltipIdx={0}
translatedName="Bugs"
value="Formated.3"
/>
<GraphsTooltipsContentIssues
index={1}
key="code_smells"
measuresHistory={Array []}
name="code_smells"
tooltipIdx={0}
translatedName="Code Smells"
value="Formated.18"
/>
<GraphsTooltipsContentIssues
index={2}
key="vulnerabilities"
measuresHistory={Array []}
name="vulnerabilities"
tooltipIdx={0}
translatedName="Vulnerabilities"
value="Formated.0"
/>
</tbody>
</table>
</div>
</Popup>
`;

exports[`should render correctly for issues graphs: with events 1`] = `
<Popup
className="disabled-pointer-events"
placement="left-top"
style={
Object {
"left": 476,
"top": 30,
"width": 250,
}
}
>
<div
className="activity-graph-tooltip"
>
<div
className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-01T22:01:00.000Z}
/>
</div>
<table
className="width-100"
>
<GraphsTooltipsContentEvents
addSeparator={true}
events={
Array [
Object {
"currentTarget": Object {
"blur": [Function],
},
"preventDefault": [Function],
"stopImmediatePropagation": [Function],
"stopPropagation": [Function],
"target": Object {
"blur": [Function],
},
},
]
}
/>
<tbody>
<GraphsTooltipsContentIssues
index={0}
key="bugs"
measuresHistory={Array []}
name="bugs"
tooltipIdx={0}
translatedName="Bugs"
value="Formated.3"
/>
<GraphsTooltipsContentIssues
index={1}
key="code_smells"
measuresHistory={Array []}
name="code_smells"
tooltipIdx={0}
translatedName="Code Smells"
value="Formated.18"
/>
<GraphsTooltipsContentIssues
index={2}
key="vulnerabilities"
measuresHistory={Array []}
name="vulnerabilities"
tooltipIdx={0}
translatedName="Vulnerabilities"
value="Formated.0"
/>
</tbody>
</table>
</div>
</Popup>
`;

exports[`should render correctly for random graphs 1`] = `
<Popup
className="disabled-pointer-events"
placement="left-top"
style={
Object {
"left": 476,
"top": 30,
"width": 250,
}
}
>
<div
className="activity-graph-tooltip"
>
<div
className="activity-graph-tooltip-title spacer-bottom"
>
<DateTimeFormatter
date={2011-10-25T10:27:41.000Z}
/>
</div>
<table
className="width-100"
>
<tbody>
<GraphsTooltipsContent
index={0}
key="bugs"
name="bugs"
translatedName="Bugs"
value="Formated.0"
/>
<GraphsTooltipsContent
index={1}
key="code_smells"
name="code_smells"
translatedName="Code Smells"
value="Formated.15"
/>
<GraphsTooltipsContent
index={2}
key="vulnerabilities"
name="vulnerabilities"
translatedName="Vulnerabilities"
value="Formated.1"
/>
</tbody>
</table>
</div>
</Popup>
`;

+ 0
- 25
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContent-test.tsx.snap View File

@@ -1,25 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<tr
className="activity-graph-tooltip-line"
key="code_smells"
>
<td
className="thin"
>
<ChartLegendIcon
className="spacer-right"
index={1}
/>
</td>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
>
1.2k
</td>
<td>
Code Smells
</td>
</tr>
`;

+ 0
- 71
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentCoverage-test.tsx.snap View File

@@ -1,71 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<tbody>
<tr
className="activity-graph-tooltip-line"
>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
colSpan={2}
>
10short_number_suffix.k
</td>
<td>
metric.uncovered_lines.name
</td>
</tr>
<tr
className="activity-graph-tooltip-line"
>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
colSpan={2}
>
80.3%
</td>
<td>
metric.coverage.name
</td>
</tr>
</tbody>
`;

exports[`should render correctly: with separator 1`] = `
<tbody>
<tr>
<td
className="activity-graph-tooltip-separator"
colSpan={3}
>
<hr />
</td>
</tr>
<tr
className="activity-graph-tooltip-line"
>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
colSpan={2}
>
10short_number_suffix.k
</td>
<td>
metric.uncovered_lines.name
</td>
</tr>
<tr
className="activity-graph-tooltip-line"
>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
colSpan={2}
>
80.3%
</td>
<td>
metric.coverage.name
</td>
</tr>
</tbody>
`;

+ 0
- 45
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentDuplication-test.tsx.snap View File

@@ -1,45 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<tbody>
<tr
className="activity-graph-tooltip-line"
>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
colSpan={2}
>
10,245.0%
</td>
<td>
metric.duplicated_lines_density.name
</td>
</tr>
</tbody>
`;

exports[`should render correctly: with separator 1`] = `
<tbody>
<tr>
<td
className="activity-graph-tooltip-separator"
colSpan={3}
>
<hr />
</td>
</tr>
<tr
className="activity-graph-tooltip-line"
>
<td
className="activity-graph-tooltip-value text-right spacer-right thin"
colSpan={2}
>
10,245.0%
</td>
<td>
metric.duplicated_lines_density.name
</td>
</tr>
</tbody>
`;

+ 0
- 60
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentEvents-test.tsx.snap View File

@@ -1,60 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<tbody>
<tr>
<td
className="activity-graph-tooltip-separator"
colSpan={3}
>
<hr />
</td>
</tr>
<tr
className="activity-graph-tooltip-line"
>
<td
colSpan={3}
>
<div
className="little-spacer-bottom"
key="1"
>
<EventInner
event={
Object {
"category": "VERSION",
"key": "1",
"name": "6.5",
}
}
readonly={true}
/>
</div>
<div
className="little-spacer-bottom"
key="2"
>
<EventInner
event={
Object {
"category": "OTHER",
"key": "2",
"name": "Foo",
}
}
readonly={true}
/>
</div>
</td>
</tr>
<tr>
<td
className="activity-graph-tooltip-separator"
colSpan={3}
>
<hr />
</td>
</tr>
</tbody>
`;

+ 0
- 34
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.tsx.snap View File

@@ -1,34 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<tr
className="activity-graph-tooltip-issues-line"
key="bugs"
>
<td
className="thin"
>
<ChartLegendIcon
className="spacer-right"
index={2}
/>
</td>
<td
className="text-right spacer-right"
>
<span
className="activity-graph-tooltip-value"
>
1.2k
</span>
<Rating
className="spacer-left"
small={true}
value="5.0"
/>
</td>
<td>
Bugs
</td>
</tr>
`;

+ 0
- 139
server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/RichQualityGateEventInner-test.tsx.snap View File

@@ -1,139 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render 1`] = `
<Fragment>
<span
className="note spacer-right"
>
event.category.QUALITY_GATE
:
</span>
<FormattedMessage
defaultMessage="event.quality_gate.still_x"
id="event.quality_gate.still_x"
values={
Object {
"status": <Level
level="ERROR"
small={true}
/>,
}
}
/>
<div>
<ResetButtonLink
className="project-activity-event-inner-more-link"
onClick={[Function]}
stopPropagation={true}
>
more
<DropdownIcon
className="little-spacer-left"
turned={false}
/>
</ResetButtonLink>
</div>
</Fragment>
`;

exports[`should render 2`] = `
<Fragment>
<span
className="note spacer-right"
>
event.category.QUALITY_GATE
:
</span>
<FormattedMessage
defaultMessage="event.quality_gate.still_x"
id="event.quality_gate.still_x"
values={
Object {
"status": <Level
level="ERROR"
small={true}
/>,
}
}
/>
<div>
<ResetButtonLink
className="project-activity-event-inner-more-link"
onClick={[Function]}
stopPropagation={true}
>
hide
<DropdownIcon
className="little-spacer-left"
turned={true}
/>
</ResetButtonLink>
</div>
<ul
className="spacer-left spacer-top"
>
<li
className="display-flex-center spacer-top"
key="foo"
>
<Level
aria-label="quality_gates.status"
className="spacer-right"
level="ERROR"
small={true}
/>
<div
className="flex-1 text-ellipsis"
>
<ForwardRef(Link)
onClick={[Function]}
title="Foo"
to={
Object {
"pathname": "/dashboard",
"search": "?id=foo&branch=master",
}
}
>
<span
aria-label="project_x.Foo"
>
Foo
</span>
</ForwardRef(Link)>
</div>
</li>
<li
className="display-flex-center spacer-top"
key="bar"
>
<Level
aria-label="quality_gates.status"
className="spacer-right"
level="ERROR"
small={true}
/>
<div
className="flex-1 text-ellipsis"
>
<ForwardRef(Link)
onClick={[Function]}
title="Bar"
to={
Object {
"pathname": "/dashboard",
"search": "?id=bar&branch=master",
}
}
>
<span
aria-label="project_x.Bar"
>
Bar
</span>
</ForwardRef(Link)>
</div>
</li>
</ul>
</Fragment>
`;

+ 2
- 2
server/sonar-web/src/main/js/components/activity-graph/styles.css View File

@@ -54,9 +54,9 @@
}

.activity-graph-legends {
flex-grow: 0;
display: flex;
justify-content: center;
padding-bottom: calc(2 * var(--gridSize));
text-align: center;
}

.activity-graph-legend-actionable {

+ 0
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1546,8 +1546,6 @@ project_activity.new_code_period_start.help=The analysis before this mark is the

project_activity.graphs.choose_type=Choose graph type
project_activity.graphs.explanation_x=This interactive graph shows data for the following project measures over time: {0}
project_activity.graphs.new_code=New Code
project_activity.graphs.new_code_long=New Code is indicated in yellow on the graph.
project_activity.graphs.issues=Issues
project_activity.graphs.coverage=Coverage
project_activity.graphs.duplications=Duplications

Loading…
Cancel
Save