Просмотр исходного кода

SONAR-20220 Show warnings for non compliant Quality Gate only when user can edit it

tags/10.2.0.77647
Zipeng WU 9 месяцев назад
Родитель
Сommit
ab5820b3aa

+ 54
- 17
server/sonar-web/src/main/js/api/mocks/QualityGatesServiceMock.ts Просмотреть файл

@@ -19,10 +19,19 @@
*/
import { cloneDeep, flatten, omit, remove } from 'lodash';
import { Project } from '../../apps/quality-gates/components/Projects';
import { mockQualityGate } from '../../helpers/mocks/quality-gates';
import {
mockQualityGate,
mockQualityGateApplicationStatus,
mockQualityGateProjectStatus,
} from '../../helpers/mocks/quality-gates';
import { mockUserBase } from '../../helpers/mocks/users';
import { mockCondition, mockGroup } from '../../helpers/testMocks';
import { MetricKey } from '../../types/metrics';
import {
QualityGateApplicationStatus,
QualityGateProjectStatus,
SearchPermissionsParameters,
} from '../../types/quality-gates';
import { CaycStatus, Condition, QualityGate } from '../../types/types';
import {
addGroup,
@@ -36,7 +45,9 @@ import {
dissociateGateWithProject,
fetchQualityGate,
fetchQualityGates,
getApplicationQualityGate,
getGateForProject,
getQualityGateProjectStatus,
renameQualityGate,
searchGroups,
searchProjects,
@@ -45,6 +56,8 @@ import {
updateCondition,
} from '../quality-gates';

jest.mock('../quality-gates');

export class QualityGatesServiceMock {
isAdmin = false;
readOnlyList: QualityGate[];
@@ -52,6 +65,8 @@ export class QualityGatesServiceMock {
projects: Project[];
getGateForProjectGateName: string;
throwOnGetGateForProject: boolean;
qualityGateProjectStatus: QualityGateProjectStatus;
applicationQualityGate: QualityGateApplicationStatus;

constructor(list?: QualityGate[]) {
this.readOnlyList = list || [
@@ -198,22 +213,27 @@ export class QualityGatesServiceMock {
this.getGateForProjectGateName = 'SonarSource way';
this.throwOnGetGateForProject = false;

(fetchQualityGate as jest.Mock).mockImplementation(this.showHandler);
(fetchQualityGates as jest.Mock).mockImplementation(this.listHandler);
(createQualityGate as jest.Mock).mockImplementation(this.createHandler);
(deleteQualityGate as jest.Mock).mockImplementation(this.destroyHandler);
(copyQualityGate as jest.Mock).mockImplementation(this.copyHandler);
jest.mocked(fetchQualityGate).mockImplementation(this.showHandler);
jest.mocked(fetchQualityGates).mockImplementation(this.listHandler);
jest.mocked(createQualityGate).mockImplementation(this.createHandler);
jest.mocked(deleteQualityGate).mockImplementation(this.destroyHandler);
jest.mocked(copyQualityGate).mockImplementation(this.copyHandler);
(renameQualityGate as jest.Mock).mockImplementation(this.renameHandler);
(createCondition as jest.Mock).mockImplementation(this.createConditionHandler);
(updateCondition as jest.Mock).mockImplementation(this.updateConditionHandler);
(deleteCondition as jest.Mock).mockImplementation(this.deleteConditionHandler);
(searchProjects as jest.Mock).mockImplementation(this.searchProjectsHandler);
(searchUsers as jest.Mock).mockImplementation(this.searchUsersHandler);
(searchGroups as jest.Mock).mockImplementation(this.searchGroupsHandler);
(associateGateWithProject as jest.Mock).mockImplementation(this.selectHandler);
(dissociateGateWithProject as jest.Mock).mockImplementation(this.deSelectHandler);
(setQualityGateAsDefault as jest.Mock).mockImplementation(this.setDefaultHandler);
jest.mocked(createCondition).mockImplementation(this.createConditionHandler);
jest.mocked(updateCondition).mockImplementation(this.updateConditionHandler);
jest.mocked(deleteCondition).mockImplementation(this.deleteConditionHandler);
jest.mocked(searchProjects).mockImplementation(this.searchProjectsHandler);
jest.mocked(searchUsers).mockImplementation(this.searchUsersHandler);
jest.mocked(searchGroups).mockImplementation(this.searchGroupsHandler);
jest.mocked(associateGateWithProject).mockImplementation(this.selectHandler);
jest.mocked(dissociateGateWithProject).mockImplementation(this.deSelectHandler);
jest.mocked(setQualityGateAsDefault).mockImplementation(this.setDefaultHandler);
(getGateForProject as jest.Mock).mockImplementation(this.projectGateHandler);
jest.mocked(getQualityGateProjectStatus).mockImplementation(this.handleQualityGetProjectStatus);
jest.mocked(getApplicationQualityGate).mockImplementation(this.handleGetApplicationQualityGate);

this.qualityGateProjectStatus = mockQualityGateProjectStatus({});
this.applicationQualityGate = mockQualityGateApplicationStatus({});

// To be implemented.
(addUser as jest.Mock).mockResolvedValue({});
@@ -416,6 +436,7 @@ export class QualityGatesServiceMock {
selected,
query,
}: {
gateName: string;
selected: string;
query: string | undefined;
}) => {
@@ -437,7 +458,7 @@ export class QualityGatesServiceMock {
return this.reply(response);
};

searchUsersHandler = ({ selected }: { selected: string }) => {
searchUsersHandler = ({ selected }: SearchPermissionsParameters) => {
if (selected === 'selected') {
return this.reply({ users: [] });
}
@@ -445,7 +466,7 @@ export class QualityGatesServiceMock {
return this.reply({ users: [mockUserBase()] });
};

searchGroupsHandler = ({ selected }: { selected: string }) => {
searchGroupsHandler = ({ selected }: SearchPermissionsParameters) => {
if (selected === 'selected') {
return this.reply({ groups: [] });
}
@@ -477,6 +498,22 @@ export class QualityGatesServiceMock {
return this.reply(this.list.find((qg) => qg.name === this.getGateForProjectGateName));
};

handleGetApplicationQualityGate = () => {
return this.reply(this.applicationQualityGate);
};

setApplicationQualityGateStatus = (status: QualityGateApplicationStatus) => {
this.applicationQualityGate = mockQualityGateApplicationStatus(status);
};

handleQualityGetProjectStatus = () => {
return this.reply(this.qualityGateProjectStatus);
};

setQualityGateProjectStatus = (status: QualityGateProjectStatus) => {
this.qualityGateProjectStatus = mockQualityGateProjectStatus(status);
};

reply<T>(response: T): Promise<T> {
return Promise.resolve(cloneDeep(response));
}

+ 5
- 6
server/sonar-web/src/main/js/api/quality-gates.ts Просмотреть файл

@@ -81,13 +81,12 @@ export function deleteCondition(data: { id: string }): Promise<void> {
return post('/api/qualitygates/delete_condition', data);
}

export function getGateForProject(data: { project: string }): Promise<QualityGate | undefined> {
export function getGateForProject(data: { project: string }): Promise<QualityGate> {
return getJSON('/api/qualitygates/get_by_project', data).then(
({ qualityGate }) =>
qualityGate && {
...qualityGate,
isDefault: qualityGate.default,
},
({ qualityGate }) => ({
...qualityGate,
isDefault: qualityGate.default,
}),
throwGlobalError
);
}

+ 18
- 2
server/sonar-web/src/main/js/apps/overview/branches/BranchOverview.tsx Просмотреть файл

@@ -22,7 +22,12 @@ import * as React from 'react';
import { getApplicationDetails, getApplicationLeak } from '../../../api/application';
import { getMeasuresWithPeriodAndMetrics } from '../../../api/measures';
import { getProjectActivity } from '../../../api/projectActivity';
import { getApplicationQualityGate, getQualityGateProjectStatus } from '../../../api/quality-gates';
import {
fetchQualityGate,
getApplicationQualityGate,
getGateForProject,
getQualityGateProjectStatus,
} from '../../../api/quality-gates';
import { getAllTimeMachineData } from '../../../api/time-machine';
import {
getActivityGraph,
@@ -47,7 +52,7 @@ import { ComponentQualifier } from '../../../types/component';
import { MetricKey } from '../../../types/metrics';
import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity';
import { QualityGateStatus, QualityGateStatusCondition } from '../../../types/quality-gates';
import { Component, MeasureEnhanced, Metric, Period } from '../../../types/types';
import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types';
import '../styles.css';
import { HISTORY_METRICS_LIST, METRICS } from '../utils';
import BranchOverviewRenderer from './BranchOverviewRenderer';
@@ -70,6 +75,7 @@ interface State {
metrics?: Metric[];
period?: Period;
qgStatuses?: QualityGateStatus[];
qualityGate?: QualityGate;
}

export const BRANCH_OVERVIEW_ACTIVITY_GRAPH = 'sonar_branch_overview.graph';
@@ -111,6 +117,7 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
this.loadApplicationStatus();
} else {
this.loadProjectStatus();
this.loadProjectQualityGate();
}
};

@@ -268,6 +275,13 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
);
};

loadProjectQualityGate = async () => {
const { component } = this.props;
const qualityGate = await getGateForProject({ project: component.key });
const qgDetails = await fetchQualityGate({ name: qualityGate.name });
this.setState({ qualityGate: qgDetails });
};

loadMeasuresAndMeta = (
componentKey: string,
branchLike?: BranchLike,
@@ -409,6 +423,7 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
metrics,
period,
qgStatuses,
qualityGate,
} = this.state;

const projectIsEmpty =
@@ -436,6 +451,7 @@ export default class BranchOverview extends React.PureComponent<Props, State> {
period={period}
projectIsEmpty={projectIsEmpty}
qgStatuses={qgStatuses}
qualityGate={qualityGate}
/>
);
}

+ 4
- 1
server/sonar-web/src/main/js/apps/overview/branches/BranchOverviewRenderer.tsx Просмотреть файл

@@ -26,7 +26,7 @@ import { Branch } from '../../../types/branch-like';
import { ComponentQualifier } from '../../../types/component';
import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity';
import { QualityGateStatus } from '../../../types/quality-gates';
import { Component, MeasureEnhanced, Metric, Period } from '../../../types/types';
import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types';
import ActivityPanel from './ActivityPanel';
import FirstAnalysisNextStepsNotif from './FirstAnalysisNextStepsNotif';
import MeasuresPanel from './MeasuresPanel';
@@ -50,6 +50,7 @@ export interface BranchOverviewRendererProps {
period?: Period;
projectIsEmpty?: boolean;
qgStatuses?: QualityGateStatus[];
qualityGate?: QualityGate;
}

export default function BranchOverviewRenderer(props: BranchOverviewRendererProps) {
@@ -70,6 +71,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
period,
projectIsEmpty,
qgStatuses,
qualityGate,
} = props;

const leakPeriod = component.qualifier === ComponentQualifier.Application ? appLeak : period;
@@ -95,6 +97,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp
component={component}
loading={loadingStatus}
qgStatuses={qgStatuses}
qualityGate={qualityGate}
/>
</div>


+ 4
- 2
server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx Просмотреть файл

@@ -23,7 +23,7 @@ import { flatMap } from 'lodash';
import * as React from 'react';
import { ComponentQualifier, isApplication } from '../../../types/component';
import { QualityGateStatus } from '../../../types/quality-gates';
import { CaycStatus, Component } from '../../../types/types';
import { CaycStatus, Component, QualityGate } from '../../../types/types';
import IgnoredConditionWarning from '../components/IgnoredConditionWarning';
import QualityGateStatusHeader from '../components/QualityGateStatusHeader';
import QualityGateStatusPassedView from '../components/QualityGateStatusPassedView';
@@ -37,10 +37,11 @@ export interface QualityGatePanelProps {
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>;
loading?: boolean;
qgStatuses?: QualityGateStatus[];
qualityGate?: QualityGate;
}

export function QualityGatePanel(props: QualityGatePanelProps) {
const { component, loading, qgStatuses = [] } = props;
const { component, loading, qgStatuses = [], qualityGate } = props;

if (qgStatuses === undefined) {
return null;
@@ -112,6 +113,7 @@ export function QualityGatePanel(props: QualityGatePanelProps) {

{qgStatuses.length === 1 &&
qgStatuses[0].caycStatus === CaycStatus.NonCompliant &&
qualityGate?.actions?.manageConditions &&
!isApp && (
<Card className="sw-mt-4 sw-body-sm">
<CleanAsYouCodeWarning component={component} />

+ 100
- 53
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx Просмотреть файл

@@ -23,11 +23,9 @@ import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { getMeasuresWithPeriodAndMetrics } from '../../../../api/measures';
import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock';
import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
import { getProjectActivity } from '../../../../api/projectActivity';
import {
getApplicationQualityGate,
getQualityGateProjectStatus,
} from '../../../../api/quality-gates';
import { getQualityGateProjectStatus } from '../../../../api/quality-gates';
import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
import { getActivityGraph, saveActivityGraph } from '../../../../components/activity-graph/utils';
import { isDiffMetric } from '../../../../helpers/measures';
@@ -84,47 +82,6 @@ jest.mock('../../../../api/measures', () => {
};
});

jest.mock('../../../../api/quality-gates', () => {
const { mockQualityGateProjectStatus, mockQualityGateApplicationStatus } = 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()),
};
});

jest.mock('../../../../api/time-machine', () => {
const { MetricKey } = jest.requireActual('../../../../types/metrics');
return {
@@ -195,19 +152,58 @@ jest.mock('../../../../components/activity-graph/utils', () => {
});

const almHandler = new AlmSettingsServiceMock();

beforeEach(jest.clearAllMocks);
let qualityGatesMock: QualityGatesServiceMock;

beforeAll(() => {
qualityGatesMock = new QualityGatesServiceMock();
qualityGatesMock.setQualityGateProjectStatus(
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',
},
],
})
);
qualityGatesMock.setApplicationQualityGateStatus(mockQualityGateApplicationStatus());
});

afterEach(() => {
jest.clearAllMocks();
qualityGatesMock.reset();
almHandler.reset();
});

describe('project overview', () => {
it('should show a successful QG', async () => {
const user = userEvent.setup();
jest
.mocked(getQualityGateProjectStatus)
.mockResolvedValueOnce(mockQualityGateProjectStatus({ status: 'OK' }));
qualityGatesMock.setQualityGateProjectStatus(
mockQualityGateProjectStatus({
status: 'OK',
})
);
renderBranchOverview();

// QG panel
@@ -236,10 +232,61 @@ describe('project overview', () => {
renderBranchOverview();

expect(await screen.findByText('metric.level.OK')).toBeInTheDocument();
expect(screen.getByText('overview.quality_gate.conditions.cayc.warning')).toBeInTheDocument();
expect(
screen.queryByText('overview.quality_gate.conditions.cayc.warning')
).not.toBeInTheDocument();
});

it('should show a successful non-compliant QG as admin', async () => {
jest
.mocked(getQualityGateProjectStatus)
.mockResolvedValueOnce(
mockQualityGateProjectStatus({ status: 'OK', caycStatus: CaycStatus.NonCompliant })
);
qualityGatesMock.setIsAdmin(true);
qualityGatesMock.setGetGateForProjectName('Non Cayc QG');

renderBranchOverview();

await screen.findByText('metric.level.OK');
expect(
await screen.findByText('overview.quality_gate.conditions.cayc.warning')
).toBeInTheDocument();
});

it('should show a failed QG', async () => {
qualityGatesMock.setQualityGateProjectStatus(
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',
},
],
})
);

renderBranchOverview();

expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
@@ -312,7 +359,7 @@ describe('application overview', () => {
},
],
});
jest.mocked(getApplicationQualityGate).mockResolvedValueOnce(appStatus);
qualityGatesMock.setApplicationQualityGateStatus(appStatus);

renderBranchOverview({ component });
expect(
@@ -380,7 +427,7 @@ it('should correctly handle graph type storage', async () => {
});

function renderBranchOverview(props: Partial<BranchOverview['props']> = {}) {
renderComponent(
return renderComponent(
<CurrentUserContextProvider currentUser={mockLoggedInUser()}>
<BranchOverview
branch={mockMainBranch()}

+ 3
- 3
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx Просмотреть файл

@@ -26,8 +26,8 @@ import withAvailableFeatures, {
import withMetricsContext from '../../../app/components/metrics/withMetricsContext';
import DocLink from '../../../components/common/DocLink';
import DocumentationTooltip from '../../../components/common/DocumentationTooltip';
import { Button } from '../../../components/controls/buttons';
import ModalButton, { ModalProps } from '../../../components/controls/ModalButton';
import { Button } from '../../../components/controls/buttons';
import { Alert } from '../../../components/ui/Alert';
import { getLocalizedMetricName, translate } from '../../../helpers/l10n';
import { Feature } from '../../../types/features';
@@ -194,8 +194,8 @@ export class Conditions extends React.PureComponent<Props, State> {
</Alert>
)}

{qualityGate.caycStatus === CaycStatus.NonCompliant && (
<Alert className="big-spacer-top big-spacer-bottom" variant="warning">
{qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
<Alert className="big-spacer-top big-spacer-bottom" variant="warning" role="alert">
<h4 className="spacer-bottom cayc-warning-header">
{translate('quality_gates.cayc_missing.banner.title')}
</h4>

+ 1
- 0
server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx Просмотреть файл

@@ -63,6 +63,7 @@ export default class Details extends React.PureComponent<Props, State> {

fetchDetails = () => {
const { qualityGateName } = this.props;

this.setState({ loading: true });
return fetchQualityGate({ name: qualityGateName }).then(
(qualityGate) => {

+ 3
- 2
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx Просмотреть файл

@@ -19,9 +19,9 @@
*/
import * as React from 'react';
import { setQualityGateAsDefault } from '../../../api/quality-gates';
import { Button } from '../../../components/controls/buttons';
import ModalButton from '../../../components/controls/ModalButton';
import Tooltip from '../../../components/controls/Tooltip';
import { Button } from '../../../components/controls/buttons';
import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
import { translate } from '../../../helpers/l10n';
import { CaycStatus, QualityGate } from '../../../types/types';
@@ -64,6 +64,7 @@ export default class DetailsHeader extends React.PureComponent<Props> {
render() {
const { qualityGate } = this.props;
const actions = qualityGate.actions || ({} as any);
const canEdit = Boolean(actions?.manageConditions);

return (
<div className="layout-page-header-panel layout-page-main-header issues-main-header">
@@ -72,7 +73,7 @@ export default class DetailsHeader extends React.PureComponent<Props> {
<div className="pull-left display-flex-center">
<h2>{qualityGate.name}</h2>
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="spacer-left" />}
{qualityGate.caycStatus === CaycStatus.NonCompliant && (
{qualityGate.caycStatus === CaycStatus.NonCompliant && canEdit && (
<Tooltip overlay={<CaycBadgeTooltip />} mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}>
<AlertWarnIcon className="spacer-left" description={<CaycBadgeTooltip />} />
</Tooltip>

+ 9
- 8
server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx Просмотреть файл

@@ -49,14 +49,15 @@ export default function List({ qualityGates, currentQualityGate }: Props) {
)}
{qualityGate.isBuiltIn && <BuiltInQualityGateBadge className="little-spacer-left" />}

{qualityGate.caycStatus === CaycStatus.NonCompliant && (
<Tooltip overlay={translate('quality_gates.cayc.tooltip.message')}>
<AlertWarnIcon
className="spacer-left"
description={translate('quality_gates.cayc.tooltip.message')}
/>
</Tooltip>
)}
{qualityGate.caycStatus === CaycStatus.NonCompliant &&
qualityGate.actions?.manageConditions && (
<Tooltip overlay={translate('quality_gates.cayc.tooltip.message')}>
<AlertWarnIcon
className="spacer-left"
description={translate('quality_gates.cayc.tooltip.message')}
/>
</Tooltip>
)}
</NavLink>
))}
</div>

+ 29
- 4
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx Просмотреть файл

@@ -22,12 +22,10 @@ import userEvent from '@testing-library/user-event';
import selectEvent from 'react-select-event';
import { QualityGatesServiceMock } from '../../../../api/mocks/QualityGatesServiceMock';
import { searchProjects, searchUsers } from '../../../../api/quality-gates';
import { renderAppRoutes, RenderContext } from '../../../../helpers/testReactTestingUtils';
import { RenderContext, renderAppRoutes } from '../../../../helpers/testReactTestingUtils';
import { Feature } from '../../../../types/features';
import routes from '../../routes';

jest.mock('../../../../api/quality-gates');

let handler: QualityGatesServiceMock;

beforeAll(() => {
@@ -346,6 +344,33 @@ it('should show warning banner when CAYC condition is not properly set and shoul
expect(overallConditionsWrapper.getByText('Complexity / Function')).toBeInTheDocument();
});

it('should not warn user when quality gate is not CAYC compliant and user has no permission to edit it', async () => {
const user = userEvent.setup();
renderQualityGateApp();

const nonCompliantQualityGate = await screen.findByRole('link', { name: 'Non Cayc QG' });

await user.click(nonCompliantQualityGate);

expect(screen.queryByRole('alert')).not.toBeInTheDocument();
expect(screen.queryByText('quality_gates.cayc.tooltip.message')).not.toBeInTheDocument();
});

it('should warn user when quality gate is not CAYC compliant and user has permission to edit it', async () => {
const user = userEvent.setup();
handler.setIsAdmin(true);
renderQualityGateApp();

const nonCompliantQualityGate = await screen.findByRole('link', { name: /Non Cayc QG/ });

await user.click(nonCompliantQualityGate);

expect(await screen.findByRole('alert')).toHaveTextContent(
/quality_gates.cayc_missing.banner.title/
);
expect(screen.getAllByText('quality_gates.cayc.tooltip.message').length).toBeGreaterThan(0);
});

it('should show success banner when quality gate is CAYC compliant', async () => {
const user = userEvent.setup();
handler.setIsAdmin(true);
@@ -626,5 +651,5 @@ describe('The Permissions section', () => {
});

function renderQualityGateApp(context?: RenderContext) {
renderAppRoutes('quality_gates', routes, context);
return renderAppRoutes('quality_gates', routes, context);
}

Загрузка…
Отмена
Сохранить