Browse Source

SONAR-17004 SONAR-17011 Write RTL ITs

tags/9.7.0.61563
Wouter Admiraal 1 year ago
parent
commit
1a4d4ee96a
15 changed files with 490 additions and 991 deletions
  1. 2
    2
      server/sonar-web/src/main/js/components/activity-graph/GraphsHeader.tsx
  2. 346
    0
      server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx
  3. 0
    39
      server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetric-test.tsx
  4. 0
    64
      server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-test.tsx
  5. 113
    0
      server/sonar-web/src/main/js/components/activity-graph/__tests__/DataTableModal-test.tsx
  6. 0
    75
      server/sonar-web/src/main/js/components/activity-graph/__tests__/EventInner-test.tsx
  7. 0
    63
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphHistory-test.tsx
  8. 0
    118
      server/sonar-web/src/main/js/components/activity-graph/__tests__/GraphsHistory-test.tsx
  9. 0
    37
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetric-test.tsx.snap
  10. 0
    93
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/AddGraphMetricPopup-test.tsx.snap
  11. 0
    105
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/EventInner-test.tsx.snap
  12. 0
    106
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphHistory-test.tsx.snap
  13. 0
    288
      server/sonar-web/src/main/js/components/activity-graph/__tests__/__snapshots__/GraphsHistory-test.tsx.snap
  14. 1
    0
      server/sonar-web/src/main/js/components/controls/Checkbox.tsx
  15. 28
    1
      server/sonar-web/src/main/js/helpers/mocks/project-activity.ts

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

@@ -67,11 +67,11 @@ export default class GraphsHeader extends React.PureComponent<Props> {
<div className={classNames(className, 'position-relative')}>
<div className="display-flex-end">
<div className="display-flex-column">
<label className="text-bold little-spacer-bottom" htmlFor="graph-select">
<label className="text-bold little-spacer-bottom" id="graph-select-label">
{translate('project_activity.graphs.choose_type')}
</label>
<Select
id="graph-select"
aria-labelledby="graph-select-label"
className="input-medium"
isSearchable={false}
onChange={this.handleGraphChange}

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

@@ -0,0 +1,346 @@
/*
* 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 { UserEvent } from '@testing-library/user-event/dist/types/setup';
import { times } from 'lodash';
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 { 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 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
})
};

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

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

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

// 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);
// Select 1 Percent type.
await clickOnMetric(user, MetricKey.coverage);

// We should see 2 graphs, correctly labelled.
expect(ui.graphs.getAll()).toHaveLength(2);

// We cannot select anymore Int types. It should hide options, and show an alert.
expect(ui.vulnerabilityCheckbox.query()).not.toBeInTheDocument();
expect(ui.hiddenOptionsAlert.get()).toBeInTheDocument();

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

// We cannot select anymore options. It should disable all remaining options, and
// show a different alert.
expect(ui.maxOptionsAlert.get()).toBeInTheDocument();
// See https://github.com/testing-library/jest-dom/issues/144 for why we cannot
// use isDisabled().
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);

// Search for option.
await searchForMetric(user, '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);

// 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();
});

it('shows the same data in a table', async () => {
const user = userEvent.setup();
renderActivityGraph();

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);
});

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 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 }));
}

async function searchForMetric(user: UserEvent, text: string) {
await user.type(ui.filterMetrics.get(), text);
}

async function removeMetric(user: UserEvent, metric: MetricKey) {
await user.click(
screen.getByRole('button', { name: `project_activity.graphs.custom.remove_metric.${metric}` })
);
}

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 [fromDate, setFromDate] = React.useState<Date | undefined>(undefined);
const [toDate, setToDate] = React.useState<Date | undefined>(undefined);

const measuresHistory: MeasureHistory[] = [];
const metrics: Metric[] = [];
[
MetricKey.bugs,
MetricKey.code_smells,
MetricKey.confirmed_issues,
MetricKey.vulnerabilities,
MetricKey.blocker_violations,
MetricKey.lines_to_cover,
MetricKey.uncovered_lines,
MetricKey.coverage,
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');
date.setDate(date.getDate() + i);
return mockHistoryItem({ date, value: i.toString() });
});
history.push(
mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200') }),
mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200') })
);
measuresHistory.push(mockMeasureHistory({ metric, history }));
metrics.push(
mockMetric({
key: metric,
name: metric,
type: metric.includes('_density') || metric === MetricKey.coverage ? 'PERCENT' : 'INT'
})
);
});

// 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' })
);

const series = generateSeries(
measuresHistory,
graph,
metrics,
getDisplayedHistoryMetrics(graph, selectedMetrics)
);
const graphs = splitSeriesInGraphs(series, MAX_GRAPHS, MAX_SERIES_PER_GRAPH);
const metricsTypeFilter =
graphs.length < MAX_GRAPHS
? undefined
: graphs.filter(graph => graph.length < MAX_SERIES_PER_GRAPH).map(graph => graph[0].type);

const addCustomMetric = (metricKey: string) => {
setSelectedMetrics([...selectedMetrics, metricKey]);
};

const removeCustomMetric = (metricKey: string) => {
setSelectedMetrics(selectedMetrics.filter(m => m !== metricKey));
};

const updateGraph = (graphType: string) => {
setGraph(graphType as GraphType);
};

const updateSelectedDate = (date?: Date) => {
setSelectedDate(date);
};

const updateFromToDates = (from?: Date, to?: Date) => {
setFromDate(from);
setToDate(to);
};

return (
<>
<GraphsHeader
addCustomMetric={addCustomMetric}
graph={graph}
metrics={metrics}
metricsTypeFilter={metricsTypeFilter}
removeCustomMetric={removeCustomMetric}
selectedMetrics={selectedMetrics}
updateGraph={updateGraph}
{...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
})
]
})
]}
graph={graph}
graphEndDate={toDate}
graphStartDate={fromDate}
graphs={graphs}
loading={false}
measuresHistory={[]}
removeCustomMetric={removeCustomMetric}
selectedDate={selectedDate}
series={series}
updateGraphZoom={updateFromToDates}
updateSelectedDate={updateSelectedDate}
{...graphsHistoryProps}
/>
</>
);
}

return renderComponent(<ActivityGraph />);
}

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

@@ -1,39 +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 { mockMetric } from '../../../helpers/testMocks';
import AddGraphMetric from '../AddGraphMetric';

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

function shallowRender(props: Partial<AddGraphMetric['props']> = {}) {
return shallow<AddGraphMetric>(
<AddGraphMetric
addMetric={jest.fn()}
metrics={[mockMetric()]}
removeMetric={jest.fn()}
selectedMetrics={[]}
{...props}
/>
);
}

+ 0
- 64
server/sonar-web/src/main/js/components/activity-graph/__tests__/AddGraphMetricPopup-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 MultiSelect from '../../common/MultiSelect';
import AddGraphMetricPopup, { AddGraphMetricPopupProps } from '../AddGraphMetricPopup';

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

it('should render correctly whith 6+ selected elements', () => {
const selectedElements = ['1', '2', '3', '4', '5', '6'];
expect(shallowRender({ selectedElements })).toMatchSnapshot();
});

it('should render correctly with type filter', () => {
const metricsTypeFilter = ['filter1', 'filter2'];
expect(shallowRender({ metricsTypeFilter })).toMatchSnapshot();
});

it('should prevent selection of unknown element', () => {
const elements = ['1', '2', '3'];
const onSelect = jest.fn();
const wrapper = shallowRender({ elements, onSelect });
wrapper
.find(MultiSelect)
.props()
.onSelect('unknown');

expect(onSelect).not.toHaveBeenCalled();
});

function shallowRender(overrides: Partial<AddGraphMetricPopupProps> = {}) {
return shallow(
<AddGraphMetricPopup
elements={[]}
filterSelected={jest.fn()}
onSearch={jest.fn()}
onSelect={jest.fn()}
onUnselect={jest.fn()}
renderLabel={element => element}
selectedElements={[]}
{...overrides}
/>
);
}

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

@@ -0,0 +1,113 @@
/*
* 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 { times } from 'lodash';
import * as React from 'react';
import { parseDate } from '../../../helpers/dates';
import { 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 DataTableModal, { DataTableModalProps } from '../DataTableModal';
import { generateSeries, getDisplayedHistoryMetrics } from '../utils';

it('should render correctly if there are no series', () => {
renderDataTableModal({ series: [] });
expect(
screen.getByText('project_activity.graphs.data_table.no_data_warning')
).toBeInTheDocument();
});

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

it('should render correctly if there is no data and we have a start date', () => {
renderDataTableModal({ graphStartDate: parseDate('3022-01-01') });
expect(
screen.getByText('project_activity.graphs.data_table.no_data_warning_check_dates_x', {
exact: false
})
).toBeInTheDocument();
});

it('should render correctly if there is no data and we have an end date', () => {
renderDataTableModal({ graphEndDate: parseDate('2015-01-01') });
expect(
screen.getByText('project_activity.graphs.data_table.no_data_warning_check_dates_y', {
exact: false
})
).toBeInTheDocument();
});

it('should render correctly if there is no data and we have a date range', () => {
renderDataTableModal({
graphEndDate: parseDate('2015-01-01'),
graphStartDate: parseDate('2014-01-01')
});
expect(
screen.getByText('project_activity.graphs.data_table.no_data_warning_check_dates_x_y', {
exact: false
})
).toBeInTheDocument();
});

function renderDataTableModal(props: Partial<DataTableModalProps> = {}) {
return renderComponent(
<DataTableModal analyses={[]} series={mockSeries()} onClose={jest.fn()} {...props} />
);
}

function mockSeries(n = 10) {
const measuresHistory: MeasureHistory[] = [];
const metrics: Metric[] = [];
[MetricKey.bugs, MetricKey.code_smells, MetricKey.vulnerabilities].forEach(metric => {
const history = times(n, i => {
const date = parseDate('2016-01-01T00:00:00+0200');
date.setDate(date.getDate() + 365 * i);
return mockHistoryItem({ date, value: i.toString() });
});
measuresHistory.push(mockMeasureHistory({ metric, history }));
metrics.push(
mockMetric({
key: metric,
name: metric,
type: 'INT'
})
);
});

return generateSeries(
measuresHistory,
GraphType.issues,
metrics,
getDisplayedHistoryMetrics(GraphType.issues, [
MetricKey.bugs,
MetricKey.code_smells,
MetricKey.vulnerabilities
])
);
}

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

@@ -1,75 +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 { mockAnalysisEvent } from '../../../helpers/mocks/project-activity';
import { BranchLike } from '../../../types/branch-like';
import EventInner, { EventInnerProps } from '../EventInner';

jest.mock('../../../app/components/componentContext/ComponentContext', () => {
const { mockBranch } = jest.requireActual('../../../helpers/mocks/branch-like');
return {
ComponentContext: {
Consumer: ({
children
}: {
children: (props: { branchLike: BranchLike }) => React.ReactNode;
}) => {
return children({ branchLike: mockBranch() });
}
}
};
});

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(
shallowRender({
event: mockAnalysisEvent({
category: 'VERSION',
description: undefined,
qualityGate: undefined
})
})
).toMatchSnapshot('no description');
expect(shallowRender({ event: mockAnalysisEvent() })).toMatchSnapshot('rich quality gate');
expect(
shallowRender({
event: mockAnalysisEvent({
category: 'DEFINITION_CHANGE',
definitionChange: {
projects: [{ changeType: 'ADDED', key: 'foo', name: 'Foo' }]
},
qualityGate: undefined
})
})
.find('Consumer')
.dive()
).toMatchSnapshot('definition change');
});

function shallowRender(props: Partial<EventInnerProps> = {}) {
return shallow(
<EventInner
event={mockAnalysisEvent({ category: 'VERSION', qualityGate: undefined })}
{...props}
/>
);
}

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

@@ -1,63 +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 GraphHistory from '../GraphHistory';
import { DEFAULT_GRAPH } from '../utils';

const SERIES = [
{
name: 'bugs',
translatedName: 'metric.bugs.name',
data: [
{ x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
{ x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
{ x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
],
type: 'INT'
}
];

it('should correctly render a graph', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ isCustom: true })).toMatchSnapshot('custom');
});

function shallowRender(overrides: Partial<GraphHistory['props']> = {}) {
return shallow(
<GraphHistory
analyses={[]}
ariaLabel="foo"
graph={DEFAULT_GRAPH}
leakPeriodDate={parseDate('2017-05-16T13:50:02+0200')}
isCustom={false}
measuresHistory={[]}
metricsType="INT"
removeCustomMetric={jest.fn()}
showAreas={true}
series={SERIES}
updateGraphZoom={jest.fn()}
updateSelectedDate={jest.fn()}
updateTooltip={jest.fn()}
{...overrides}
/>
);
}

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

@@ -1,118 +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 GraphsHistory from '../GraphsHistory';
import { DEFAULT_GRAPH } from '../utils';

const ANALYSES = [
{
key: 'A1',
date: parseDate('2016-10-27T16:33:50+0200'),
events: [
{
key: 'E1',
category: 'VERSION',
name: '6.5-SNAPSHOT'
}
]
},
{
key: 'A2',
date: parseDate('2016-10-27T12:21:15+0200'),
events: []
},
{
key: 'A3',
date: parseDate('2016-10-26T12:17:29+0200'),
events: [
{
key: 'E2',
category: 'OTHER',
name: 'foo'
},
{
key: 'E3',
category: 'VERSION',
name: '6.4'
}
]
}
];

const SERIES = [
{
name: 'bugs',
translatedName: 'metric.bugs.name',
data: [
{ x: parseDate('2016-10-27T16:33:50+0200'), y: 5 },
{ x: parseDate('2016-10-27T12:21:15+0200'), y: 16 },
{ x: parseDate('2016-10-26T12:17:29+0200'), y: 12 }
],
type: 'INT'
}
];

const DEFAULT_PROPS: GraphsHistory['props'] = {
analyses: ANALYSES,
graph: DEFAULT_GRAPH,
graphs: [SERIES],
leakPeriodDate: parseDate('2017-05-16T13:50:02+0200'),
loading: false,
measuresHistory: [],
removeCustomMetric: () => {},
series: SERIES,
updateGraphZoom: () => {},
updateSelectedDate: () => {}
};

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

it('should correctly render multiple graphs', () => {
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} graphs={[SERIES, SERIES]} />)).toMatchSnapshot();
});

it('should show a loading view instead of the graph', () => {
expect(
shallow(<GraphsHistory {...DEFAULT_PROPS} loading={true} />).find('DeferredSpinner')
).toHaveLength(1);
});

it('should show that there is no history data', () => {
expect(shallow(<GraphsHistory {...DEFAULT_PROPS} series={[]} />)).toMatchSnapshot();
expect(
shallow(
<GraphsHistory
{...DEFAULT_PROPS}
series={[
{
name: 'bugs',
translatedName: 'metric.bugs.name',
data: [{ x: parseDate('2016-10-27T16:33:50+0200'), y: undefined }],
type: 'INT'
}
]}
/>
)
).toMatchSnapshot();
});

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

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

exports[`should render correctly 1`] = `
<Dropdown
className="display-inline-block"
closeOnClick={false}
closeOnClickOutside={true}
overlay={
<AddGraphMetricPopup
elements={
Array [
"coverage",
]
}
filterSelected={[Function]}
onSearch={[Function]}
onSelect={[Function]}
onUnselect={[Function]}
renderLabel={[Function]}
selectedElements={Array []}
/>
}
>
<Button
className="spacer-left"
>
<span
className="text-ellipsis text-middle"
>
project_activity.graphs.custom.add
</span>
<DropdownIcon
className="text-top little-spacer-left"
/>
</Button>
</Dropdown>
`;

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

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

exports[`should render correctly 1`] = `
<div
className="menu abs-width-300"
>
<MultiSelect
allowNewElements={false}
allowSelection={true}
elements={Array []}
filterSelected={[MockFunction]}
footerNode=""
legend="project_activity.graphs.custom.select_metric"
listSize={0}
onSearch={[MockFunction]}
onSelect={[Function]}
onUnselect={[MockFunction]}
placeholder="search.search_for_metrics"
renderLabel={[Function]}
selectedElements={Array []}
validateSearchInput={[Function]}
/>
</div>
`;

exports[`should render correctly whith 6+ selected elements 1`] = `
<div
className="menu abs-width-300"
>
<MultiSelect
allowNewElements={false}
allowSelection={false}
elements={Array []}
filterSelected={[MockFunction]}
footerNode={
<Alert
className="spacer-left spacer-right spacer-top"
variant="info"
>
project_activity.graphs.custom.add_metric_info
</Alert>
}
legend="project_activity.graphs.custom.select_metric"
listSize={0}
onSearch={[MockFunction]}
onSelect={[Function]}
onUnselect={[MockFunction]}
placeholder="search.search_for_metrics"
renderLabel={[Function]}
selectedElements={
Array [
"1",
"2",
"3",
"4",
"5",
"6",
]
}
validateSearchInput={[Function]}
/>
</div>
`;

exports[`should render correctly with type filter 1`] = `
<div
className="menu abs-width-300"
>
<MultiSelect
allowNewElements={false}
allowSelection={true}
elements={Array []}
filterSelected={[MockFunction]}
footerNode={
<Alert
className="spacer-left spacer-right spacer-top"
variant="info"
>
project_activity.graphs.custom.type_x_message.metric.type.filter1, metric.type.filter2
</Alert>
}
legend="project_activity.graphs.custom.select_metric"
listSize={0}
onSearch={[MockFunction]}
onSelect={[Function]}
onUnselect={[MockFunction]}
placeholder="search.search_for_metrics"
renderLabel={[Function]}
selectedElements={Array []}
validateSearchInput={[Function]}
/>
</div>
`;

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

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

exports[`should render correctly: default 1`] = `
<Tooltip
overlay="Lorem ipsum dolor sit amet"
>
<span
className="text-middle"
>
<span
className="note little-spacer-right"
>
event.category.VERSION
:
</span>
<strong
className="spacer-right"
>
Lorem ipsum
</strong>
</span>
</Tooltip>
`;

exports[`should render correctly: definition change 1`] = `
<DefinitionChangeEventInner
branchLike={
Object {
"analysisDate": "2018-01-01",
"excludedFromPurge": true,
"isMain": false,
"name": "branch-6.7",
}
}
event={
Object {
"category": "DEFINITION_CHANGE",
"definitionChange": Object {
"projects": Array [
Object {
"changeType": "ADDED",
"key": "foo",
"name": "Foo",
},
],
},
"description": "Lorem ipsum dolor sit amet",
"key": "E11",
"name": "Lorem ipsum",
"qualityGate": undefined,
}
}
/>
`;

exports[`should render correctly: no description 1`] = `
<Tooltip
overlay={null}
>
<span
className="text-middle"
>
<span
className="note little-spacer-right"
>
event.category.VERSION
:
</span>
<strong
className="spacer-right"
>
Lorem ipsum
</strong>
</span>
</Tooltip>
`;

exports[`should render correctly: rich quality gate 1`] = `
<RichQualityGateEventInner
event={
Object {
"category": "QUALITY_GATE",
"description": "Lorem ipsum dolor sit amet",
"key": "E11",
"name": "Lorem ipsum",
"qualityGate": Object {
"failing": Array [
Object {
"branch": "master",
"key": "foo",
"name": "Foo",
},
Object {
"branch": "feature/bar",
"key": "bar",
"name": "Bar",
},
],
"status": "ERROR",
"stillFailing": true,
},
}
}
/>
`;

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

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

exports[`should correctly render a graph 1`] = `
<div
className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<GraphsLegendStatic
series={
Array [
Object {
"data": Array [
Object {
"x": 2016-10-27T14:33:50.000Z,
"y": 5,
},
Object {
"x": 2016-10-27T10:21:15.000Z,
"y": 16,
},
Object {
"x": 2016-10-26T10:17:29.000Z,
"y": 12,
},
],
"name": "bugs",
"translatedName": "metric.bugs.name",
"type": "INT",
},
]
}
/>
<div
className="flex-1"
>
<AutoSizer>
<Component />
</AutoSizer>
</div>
<div
className="little-spacer-top big-spacer-bottom"
>
<div
className="display-flex-justify-end little-padded-right"
>
<ModalButton
modal={[Function]}
>
<Component />
</ModalButton>
</div>
</div>
</div>
`;

exports[`should correctly render a graph: custom 1`] = `
<div
className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<GraphsLegendCustom
removeMetric={[MockFunction]}
series={
Array [
Object {
"data": Array [
Object {
"x": 2016-10-27T14:33:50.000Z,
"y": 5,
},
Object {
"x": 2016-10-27T10:21:15.000Z,
"y": 16,
},
Object {
"x": 2016-10-26T10:17:29.000Z,
"y": 12,
},
],
"name": "bugs",
"translatedName": "metric.bugs.name",
"type": "INT",
},
]
}
/>
<div
className="flex-1"
>
<AutoSizer>
<Component />
</AutoSizer>
</div>
<div
className="little-spacer-top big-spacer-bottom"
>
<div
className="display-flex-justify-end little-padded-right"
>
<ModalButton
modal={[Function]}
>
<Component />
</ModalButton>
</div>
</div>
</div>
`;

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

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

exports[`should correctly render a graph 1`] = `
<div
className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
>
<GraphHistory
analyses={
Array [
Object {
"date": 2016-10-27T14:33:50.000Z,
"events": Array [
Object {
"category": "VERSION",
"key": "E1",
"name": "6.5-SNAPSHOT",
},
],
"key": "A1",
},
Object {
"date": 2016-10-27T10:21:15.000Z,
"events": Array [],
"key": "A2",
},
Object {
"date": 2016-10-26T10:17:29.000Z,
"events": Array [
Object {
"category": "OTHER",
"key": "E2",
"name": "foo",
},
Object {
"category": "VERSION",
"key": "E3",
"name": "6.4",
},
],
"key": "A3",
},
]
}
ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
graph="issues"
isCustom={false}
key="0"
leakPeriodDate={2017-05-16T11:50:02.000Z}
measuresHistory={Array []}
metricsType="INT"
removeCustomMetric={[Function]}
series={
Array [
Object {
"data": Array [
Object {
"x": 2016-10-27T14:33:50.000Z,
"y": 5,
},
Object {
"x": 2016-10-27T10:21:15.000Z,
"y": 16,
},
Object {
"x": 2016-10-26T10:17:29.000Z,
"y": 12,
},
],
"name": "bugs",
"translatedName": "metric.bugs.name",
"type": "INT",
},
]
}
showAreas={false}
updateGraphZoom={[Function]}
updateSelectedDate={[Function]}
updateTooltip={[Function]}
/>
</div>
`;

exports[`should correctly render multiple graphs 1`] = `
<div
className="display-flex-justify-center display-flex-column display-flex-stretch flex-grow"
>
<GraphHistory
analyses={
Array [
Object {
"date": 2016-10-27T14:33:50.000Z,
"events": Array [
Object {
"category": "VERSION",
"key": "E1",
"name": "6.5-SNAPSHOT",
},
],
"key": "A1",
},
Object {
"date": 2016-10-27T10:21:15.000Z,
"events": Array [],
"key": "A2",
},
Object {
"date": 2016-10-26T10:17:29.000Z,
"events": Array [
Object {
"category": "OTHER",
"key": "E2",
"name": "foo",
},
Object {
"category": "VERSION",
"key": "E3",
"name": "6.4",
},
],
"key": "A3",
},
]
}
ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
graph="issues"
isCustom={false}
key="0"
leakPeriodDate={2017-05-16T11:50:02.000Z}
measuresHistory={Array []}
metricsType="INT"
removeCustomMetric={[Function]}
series={
Array [
Object {
"data": Array [
Object {
"x": 2016-10-27T14:33:50.000Z,
"y": 5,
},
Object {
"x": 2016-10-27T10:21:15.000Z,
"y": 16,
},
Object {
"x": 2016-10-26T10:17:29.000Z,
"y": 12,
},
],
"name": "bugs",
"translatedName": "metric.bugs.name",
"type": "INT",
},
]
}
showAreas={false}
updateGraphZoom={[Function]}
updateSelectedDate={[Function]}
updateTooltip={[Function]}
/>
<GraphHistory
analyses={
Array [
Object {
"date": 2016-10-27T14:33:50.000Z,
"events": Array [
Object {
"category": "VERSION",
"key": "E1",
"name": "6.5-SNAPSHOT",
},
],
"key": "A1",
},
Object {
"date": 2016-10-27T10:21:15.000Z,
"events": Array [],
"key": "A2",
},
Object {
"date": 2016-10-26T10:17:29.000Z,
"events": Array [
Object {
"category": "OTHER",
"key": "E2",
"name": "foo",
},
Object {
"category": "VERSION",
"key": "E3",
"name": "6.4",
},
],
"key": "A3",
},
]
}
ariaLabel="project_activity.graphs.explanation_x.metric.bugs.name"
graph="issues"
isCustom={false}
key="1"
leakPeriodDate={2017-05-16T11:50:02.000Z}
measuresHistory={Array []}
metricsType="INT"
removeCustomMetric={[Function]}
series={
Array [
Object {
"data": Array [
Object {
"x": 2016-10-27T14:33:50.000Z,
"y": 5,
},
Object {
"x": 2016-10-27T10:21:15.000Z,
"y": 16,
},
Object {
"x": 2016-10-26T10:17:29.000Z,
"y": 12,
},
],
"name": "bugs",
"translatedName": "metric.bugs.name",
"type": "INT",
},
]
}
showAreas={false}
updateGraphZoom={[Function]}
updateSelectedDate={[Function]}
updateTooltip={[Function]}
/>
</div>
`;

exports[`should show that there is no history data 1`] = `
<div
className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<div
className="display-flex-center display-flex-justify-center"
>
<img
alt=""
className="spacer-right"
height={52}
src="/images/activity-chart.svg"
/>
<div
className="big-spacer-left big text-muted"
style={
Object {
"maxWidth": 300,
}
}
>
component_measures.no_history
</div>
</div>
</div>
`;

exports[`should show that there is no history data 2`] = `
<div
className="activity-graph-container flex-grow display-flex-column display-flex-stretch display-flex-justify-center"
>
<div
className="display-flex-center display-flex-justify-center"
>
<img
alt=""
className="spacer-right"
height={52}
src="/images/activity-chart.svg"
/>
<div
className="big-spacer-left big text-muted"
style={
Object {
"maxWidth": 300,
}
}
>
component_measures.no_history
</div>
</div>
</div>
`;

+ 1
- 0
server/sonar-web/src/main/js/components/controls/Checkbox.tsx View File

@@ -75,6 +75,7 @@ export default class Checkbox extends React.PureComponent<Props> {
<a
aria-checked={thirdState ? 'mixed' : checked}
aria-label={label}
aria-disabled={disabled}
className={classNames('link-checkbox', this.props.className, {
disabled
})}

+ 28
- 1
server/sonar-web/src/main/js/helpers/mocks/project-activity.ts View File

@@ -18,7 +18,14 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { Analysis, AnalysisEvent, ParsedAnalysis } from '../../types/project-activity';
import {
Analysis,
AnalysisEvent,
HistoryItem,
MeasureHistory,
ParsedAnalysis
} from '../../types/project-activity';
import { parseDate } from '../dates';

export function mockAnalysis(overrides: Partial<Analysis> = {}): Analysis {
return {
@@ -65,3 +72,23 @@ export function mockAnalysisEvent(overrides: Partial<AnalysisEvent> = {}): Analy
...overrides
};
}

export function mockMeasureHistory(overrides: Partial<MeasureHistory> = {}): MeasureHistory {
return {
metric: 'code_smells',
history: [
mockHistoryItem(),
mockHistoryItem({ date: parseDate('2018-10-27T12:21:15+0200'), value: '1749' }),
mockHistoryItem({ date: parseDate('2020-10-27T16:33:50+0200'), value: '500' })
],
...overrides
};
}

export function mockHistoryItem(overrides: Partial<HistoryItem> = {}): HistoryItem {
return {
date: parseDate('2016-10-26T12:17:29+0200'),
value: '2286',
...overrides
};
}

Loading…
Cancel
Save