/* * SonarQube * Copyright (C) 2009-2024 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, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock'; import { fetchQualityGate, getQualityGateProjectStatus } from '../../../../api/quality-gates'; import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider'; import { mockPullRequest } from '../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockQualityGate, mockQualityGateProjectCondition, } from '../../../../helpers/mocks/quality-gates'; import { mockLoggedInUser, mockMeasure, mockMetric } from '../../../../helpers/testMocks'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import { byLabelText, byRole } from '../../../../helpers/testSelector'; import { ComponentPropsType } from '../../../../helpers/testUtils'; import { ComponentQualifier } from '../../../../types/component'; import { MetricKey, MetricType } from '../../../../types/metrics'; import { CaycStatus } from '../../../../types/types'; import { NoticeType } from '../../../../types/users'; import PullRequestOverview from '../PullRequestOverview'; jest.mock('../../../../api/measures', () => { return { ...jest.requireActual('../../../../types/metrics'), getMeasuresWithMetrics: jest.fn().mockResolvedValue({ component: { key: '', name: '', qualifier: ComponentQualifier.Project, measures: [ mockMeasure({ metric: MetricKey.new_coverage, }), mockMeasure({ metric: MetricKey.duplicated_lines, }), mockMeasure({ metric: MetricKey.new_bugs, }), mockMeasure({ metric: MetricKey.new_lines, }), mockMeasure({ metric: MetricKey.new_violations, }), mockMeasure({ metric: MetricKey.pull_request_fixed_issues, }), ], }, metrics: [ mockMetric({ key: MetricKey.new_coverage }), mockMetric({ key: MetricKey.duplicated_lines }), mockMetric({ key: MetricKey.new_lines, type: MetricType.ShortInteger }), mockMetric({ key: MetricKey.new_bugs, type: MetricType.Integer }), mockMetric({ key: MetricKey.new_violations }), mockMetric({ key: MetricKey.pull_request_fixed_issues }), ], }), }; }); jest.mock('../../../../api/quality-gates', () => { const { mockQualityGateProjectStatus, mockQualityGateApplicationStatus, mockQualityGate } = jest.requireActual('../../../../helpers/mocks/quality-gates'); const { MetricKey } = jest.requireActual('../../../../types/metrics'); return { getQualityGateProjectStatus: jest.fn().mockResolvedValue( mockQualityGateProjectStatus({ status: 'ERROR', conditions: [ { actualValue: '2', comparator: 'GT', errorThreshold: '1', metricKey: MetricKey.new_reliability_rating, periodIndex: 1, status: 'ERROR', }, { actualValue: '5', comparator: 'GT', errorThreshold: '2.0', metricKey: MetricKey.bugs, periodIndex: 0, status: 'ERROR', }, { actualValue: '2', comparator: 'GT', errorThreshold: '1.0', metricKey: 'unknown_metric', periodIndex: 0, status: 'ERROR', }, ], }), ), getApplicationQualityGate: jest.fn().mockResolvedValue(mockQualityGateApplicationStatus()), getGateForProject: jest.fn().mockResolvedValue(mockQualityGate({ isBuiltIn: true })), fetchQualityGate: jest.fn().mockResolvedValue(mockQualityGate({ isBuiltIn: true })), }; }); const branchesHandler = new BranchesServiceMock(); afterEach(() => { branchesHandler.reset(); }); it('should render links correctly', async () => { jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'OK', conditions: [], caycStatus: CaycStatus.Compliant, ignoredConditions: false, }); renderPullRequestOverview(); await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument()); expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument(); expect( byRole('link', { name: 'overview.see_more_details_on_x_of_y.1.metric.new_violations.name', }).get(), ).toHaveAttribute( 'href', '/project/issues?pullRequest=1001&issueStatuses=OPEN%2CCONFIRMED&id=foo', ); expect( byRole('link', { name: 'overview.see_more_details_on_x_of_y.1.metric.pull_request_fixed_issues.name', }).get(), ).toHaveAttribute('href', '/project/issues?fixedInPullRequest=1001&id=foo'); expect( screen.getByRole('link', { name: 'no_measure_value_x.metric.new_security_hotspots.name' }), ).toBeInTheDocument(); expect( screen.getByRole('link', { name: 'no_measure_value_x.metric.new_accepted_issues.name' }), ).toBeInTheDocument(); expect( screen.getByRole('link', { name: 'no_measure_value_x.metric.new_duplicated_lines_density.name', }), ).toBeInTheDocument(); }); it('should render correctly for a passed QG', async () => { jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'OK', conditions: [], caycStatus: CaycStatus.Compliant, ignoredConditions: false, }); renderPullRequestOverview(); await waitFor(async () => expect(await screen.findByText('metric.level.OK')).toBeInTheDocument()); expect(screen.getByLabelText('overview.quality_gate_x.overview.gate.OK')).toBeInTheDocument(); expect(screen.getByText('metric.new_lines.name')).toBeInTheDocument(); expect(screen.getByText(/overview.last_analysis_x/)).toBeInTheDocument(); }); it('should render correctly if conditions are ignored', async () => { jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'OK', conditions: [], caycStatus: CaycStatus.Compliant, ignoredConditions: true, }); renderPullRequestOverview(); await waitFor(async () => expect(await screen.findByText('overview.quality_gate.ignored_conditions')).toBeInTheDocument(), ); }); it('should render correctly for a failed QG', async () => { jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'ERROR', conditions: [ mockQualityGateProjectCondition({ errorThreshold: '2.0', metricKey: MetricKey.new_coverage, periodIndex: 1, }), mockQualityGateProjectCondition({ errorThreshold: '1.0', metricKey: MetricKey.duplicated_lines, periodIndex: 1, }), mockQualityGateProjectCondition({ errorThreshold: '3', metricKey: MetricKey.new_bugs, periodIndex: 1, }), ], caycStatus: CaycStatus.Compliant, ignoredConditions: true, }); renderPullRequestOverview(); await waitFor(async () => expect( await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(), ).toBeInTheDocument(), ); expect( byRole('link', { name: 'overview.measures.failed_badge overview.failed_condition.x_required 10.0% duplicated_lines≤ 1.0%', }).get(), ).toBeInTheDocument(); expect( byRole('link', { name: 'overview.measures.failed_badge overview.failed_condition.x_required 10 new_bugs≤ 3', }).get(), ).toBeInTheDocument(); }); it('renders SL promotion', async () => { const user = userEvent.setup(); jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'ERROR', conditions: [ mockQualityGateProjectCondition({ errorThreshold: '2.0', metricKey: MetricKey.new_coverage, periodIndex: 1, }), ], caycStatus: CaycStatus.Compliant, ignoredConditions: true, }); renderPullRequestOverview(); await waitFor(async () => expect( await byRole('heading', { name: 'overview.sonarlint_ad.header' }).find(), ).toBeInTheDocument(), ); // Close promotion await user.click(byRole('button', { name: 'overview.sonarlint_ad.close_promotion' }).get()); expect( byRole('heading', { name: 'overview.sonarlint_ad.header' }).query(), ).not.toBeInTheDocument(); }); it('should render correctly 0 New issues onboarding', async () => { jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'ERROR', conditions: [ mockQualityGateProjectCondition({ status: 'ERROR', errorThreshold: '0', metricKey: MetricKey.new_violations, actualValue: '1', }), ], caycStatus: CaycStatus.Compliant, ignoredConditions: false, }); jest.mocked(fetchQualityGate).mockResolvedValueOnce(mockQualityGate({ isBuiltIn: true })); renderPullRequestOverview(); expect( await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(), ).toBeInTheDocument(); expect(await byRole('alertdialog').find()).toBeInTheDocument(); }); it('should not render 0 New issues onboarding when user dismissed it', async () => { jest.mocked(getQualityGateProjectStatus).mockResolvedValueOnce({ status: 'ERROR', conditions: [ mockQualityGateProjectCondition({ status: 'ERROR', errorThreshold: '0', metricKey: MetricKey.new_violations, actualValue: '1', }), ], caycStatus: CaycStatus.Compliant, ignoredConditions: false, }); jest.mocked(fetchQualityGate).mockResolvedValueOnce(mockQualityGate({ isBuiltIn: true })); renderPullRequestOverview( {}, mockLoggedInUser({ dismissedNotices: { [NoticeType.OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION]: true }, }), ); await waitFor(async () => expect( await byLabelText('overview.quality_gate_x.overview.gate.ERROR').find(), ).toBeInTheDocument(), ); expect(await byRole('alertdialog').query()).not.toBeInTheDocument(); }); function renderPullRequestOverview( props: Partial> = {}, currentUser = mockLoggedInUser(), ) { renderComponent( , ); }