diff options
Diffstat (limited to 'server/sonar-web/src/main/js/apps/quality-gates/components')
15 files changed, 1420 insertions, 137 deletions
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx index 84a6e8b4e94..fc914abf50d 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Condition.tsx @@ -18,9 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import ActionsDropdown, { - ActionsDropdownItem -} from 'sonar-ui-common/components/controls/ActionsDropdown'; +import { DeleteButton, EditButton } from 'sonar-ui-common/components/controls/buttons'; import ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal'; import { getLocalizedMetricName, @@ -29,6 +27,7 @@ import { } from 'sonar-ui-common/helpers/l10n'; import { formatMeasure } from 'sonar-ui-common/helpers/measures'; import { deleteCondition } from '../../../api/quality-gates'; +import { getLocalizedMetricNameNoDiffMetric } from '../utils'; import ConditionModal from './ConditionModal'; interface Props { @@ -99,29 +98,30 @@ export default class Condition extends React.PureComponent<Props, State> { return ( <tr> <td className="text-middle"> - {getLocalizedMetricName(metric)} + {getLocalizedMetricNameNoDiffMetric(metric)} {metric.hidden && ( <span className="text-danger little-spacer-left">{translate('deprecated')}</span> )} </td> - <td className="thin text-middle nowrap">{this.renderOperator()}</td> + <td className="text-middle nowrap">{this.renderOperator()}</td> - <td className="thin text-middle nowrap">{formatMeasure(condition.error, metric.type)}</td> + <td className="text-middle nowrap">{formatMeasure(condition.error, metric.type)}</td> {canEdit && ( - <td className="thin text-middle nowrap"> - <ActionsDropdown className="dropdown-menu-right"> - <ActionsDropdownItem className="js-condition-update" onClick={this.handleOpenUpdate}> - {translate('update_details')} - </ActionsDropdownItem> - <ActionsDropdownItem - destructive={true} - id="condition-delete" - onClick={this.handleDeleteClick}> - {translate('delete')} - </ActionsDropdownItem> - </ActionsDropdown> + <> + <td className="text-center thin"> + <EditButton + data-test="quality-gates__condition-update" + onClick={this.handleOpenUpdate} + /> + </td> + <td className="text-center thin"> + <DeleteButton + data-test="quality-gates__condition-delete" + onClick={this.handleDeleteClick} + /> + </td> {this.state.modal && ( <ConditionModal condition={condition} @@ -147,7 +147,7 @@ export default class Condition extends React.PureComponent<Props, State> { )} </ConfirmModal> )} - </td> + </> )} </tr> ); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx index ba87f3434a8..3ee164f96a7 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ConditionModal.tsx @@ -19,8 +19,10 @@ */ import * as React from 'react'; import ConfirmModal from 'sonar-ui-common/components/controls/ConfirmModal'; +import Radio from 'sonar-ui-common/components/controls/Radio'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; +import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; import { createCondition, updateCondition } from '../../../api/quality-gates'; import { getPossibleOperators } from '../utils'; import ConditionOperator from './ConditionOperator'; @@ -43,28 +45,20 @@ interface State { errorMessage?: string; metric?: T.Metric; op?: string; + scope: 'new' | 'overall'; } export default class ConditionModal extends React.PureComponent<Props, State> { - mounted = false; - constructor(props: Props) { super(props); this.state = { error: props.condition ? props.condition.error : '', + scope: 'new', metric: props.metric ? props.metric : undefined, op: props.condition ? props.condition.op : undefined }; } - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - getSinglePossibleOperator(metric: T.Metric) { const operators = getPossibleOperators(metric); return Array.isArray(operators) ? undefined : operators; @@ -86,6 +80,21 @@ export default class ConditionModal extends React.PureComponent<Props, State> { return Promise.reject(); }; + handleScopeChange = (scope: 'new' | 'overall') => { + this.setState(({ metric }) => { + const { metrics } = this.props; + let correspondingMetric; + + if (metric && metrics) { + const correspondingMetricKey = + scope === 'new' ? `new_${metric.key}` : metric.key.replace(/^new_/, ''); + correspondingMetric = metrics.find(m => m.key === correspondingMetricKey); + } + + return { scope, metric: correspondingMetric }; + }); + }; + handleMetricChange = (metric: T.Metric) => { this.setState({ metric, op: undefined, error: '' }); }; @@ -100,7 +109,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> { render() { const { header, metrics, onClose } = this.props; - const { op, error, metric } = this.state; + const { op, error, scope, metric } = this.state; return ( <ConfirmModal confirmButtonText={header} @@ -110,13 +119,44 @@ export default class ConditionModal extends React.PureComponent<Props, State> { onConfirm={this.handleFormSubmit} size="small"> {this.state.errorMessage && <Alert variant="error">{this.state.errorMessage}</Alert>} + + {this.props.metric === undefined && ( + <div className="modal-field display-flex-center"> + <Radio checked={scope === 'new'} onCheck={this.handleScopeChange} value="new"> + <span data-test="quality-gates__condition-scope-new"> + {translate('quality_gates.conditions.new_code')} + </span> + </Radio> + <Radio + checked={scope === 'overall'} + className="big-spacer-left" + onCheck={this.handleScopeChange} + value="overall"> + <span data-test="quality-gates__condition-scope-overall"> + {translate('quality_gates.conditions.overall_code')} + </span> + </Radio> + </div> + )} + <div className="modal-field"> - <label htmlFor="condition-metric">{translate('quality_gates.conditions.metric')}</label> - {metrics && <MetricSelect metrics={metrics} onMetricChange={this.handleMetricChange} />} + <label htmlFor="condition-metric"> + {translate('quality_gates.conditions.fails_when')} + </label> + {metrics && ( + <MetricSelect + metric={metric} + metrics={metrics.filter(metric => + scope === 'new' ? isDiffMetric(metric.key) : !isDiffMetric(metric.key) + )} + onMetricChange={this.handleMetricChange} + /> + )} {this.props.metric && ( <span className="note">{getLocalizedMetricName(this.props.metric)}</span> )} </div> + {metric && ( <> <div className="modal-field display-inline-block"> @@ -131,7 +171,7 @@ export default class ConditionModal extends React.PureComponent<Props, State> { </div> <div className="modal-field display-inline-block spacer-left"> <label htmlFor="condition-threshold"> - {translate('quality_gates.conditions.error')} + {translate('quality_gates.conditions.value')} </label> <ThresholdInput metric={metric} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx index 5c04b8148a6..bb12fa3b403 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx @@ -23,17 +23,20 @@ import { Button } from 'sonar-ui-common/components/controls/buttons'; import ModalButton from 'sonar-ui-common/components/controls/ModalButton'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; +import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; import DocTooltip from '../../../components/docs/DocTooltip'; +import { withAppState } from '../../../components/hoc/withAppState'; import Condition from './Condition'; import ConditionModal from './ConditionModal'; interface Props { + appState: Pick<T.AppState, 'branchesEnabled'>; canEdit: boolean; conditions: T.Condition[]; metrics: T.Dict<T.Metric>; onAddCondition: (condition: T.Condition) => void; - onSaveCondition: (newCondition: T.Condition, oldCondition: T.Condition) => void; onRemoveCondition: (Condition: T.Condition) => void; + onSaveCondition: (newCondition: T.Condition, oldCondition: T.Condition) => void; organization?: string; qualityGate: T.QualityGate; } @@ -41,21 +44,67 @@ interface Props { const FORBIDDEN_METRIC_TYPES = ['DATA', 'DISTRIB', 'STRING', 'BOOL']; const FORBIDDEN_METRICS = ['alert_status', 'releasability_rating', 'security_review_rating']; -export default class Conditions extends React.PureComponent<Props> { - getConditionKey = (condition: T.Condition, index: number) => { - return condition.id ? condition.id : `new-${index}`; +export class Conditions extends React.PureComponent<Props> { + renderConditionsTable = (conditions: T.Condition[], scope: 'new' | 'overall') => { + const { + qualityGate, + metrics, + canEdit, + onRemoveCondition, + onSaveCondition, + organization + } = this.props; + return ( + <table className="data zebra" data-test={`quality-gates__conditions-${scope}`}> + <thead> + <tr> + <th className="nowrap" style={{ width: 300 }}> + {translate('quality_gates.conditions.metric')} + </th> + <th className="nowrap">{translate('quality_gates.conditions.operator')}</th> + <th className="nowrap">{translate('quality_gates.conditions.value')}</th> + {canEdit && ( + <> + <th className="thin">{translate('edit')}</th> + <th className="thin">{translate('delete')}</th> + </> + )} + </tr> + </thead> + <tbody> + {conditions.map(condition => ( + <Condition + canEdit={canEdit} + condition={condition} + key={condition.id} + metric={metrics[condition.metric]} + onRemoveCondition={onRemoveCondition} + onSaveCondition={onSaveCondition} + organization={organization} + qualityGate={qualityGate} + /> + ))} + </tbody> + </table> + ); }; render() { - const { qualityGate, conditions, metrics, canEdit, organization } = this.props; + const { appState, conditions, metrics, canEdit } = this.props; const existingConditions = conditions.filter(condition => metrics[condition.metric]); - const sortedConditions = sortBy( existingConditions, condition => metrics[condition.metric] && metrics[condition.metric].name ); + const sortedConditionsOnOverallMetrics = sortedConditions.filter( + condition => !isDiffMetric(condition.metric) + ); + const sortedConditionsOnNewMetrics = sortedConditions.filter(condition => + isDiffMetric(condition.metric) + ); + const duplicates: T.Condition[] = []; const savedConditions = existingConditions.filter(condition => condition.id != null); savedConditions.forEach(condition => { @@ -82,7 +131,7 @@ export default class Conditions extends React.PureComponent<Props> { ); return ( - <div className="quality-gate-section" id="quality-gate-conditions"> + <div className="quality-gate-section"> {canEdit && ( <div className="pull-right"> <ModalButton @@ -97,11 +146,14 @@ export default class Conditions extends React.PureComponent<Props> { /> )}> {({ onClick }) => ( - <Button onClick={onClick}>{translate('quality_gates.add_condition')}</Button> + <Button data-test="quality-gates__add-condition" onClick={onClick}> + {translate('quality_gates.add_condition')} + </Button> )} </ModalButton> </div> )} + <header className="display-flex-center spacer-bottom"> <h3>{translate('quality_gates.conditions')}</h3> <DocTooltip @@ -110,8 +162,6 @@ export default class Conditions extends React.PureComponent<Props> { /> </header> - <div className="big-spacer-bottom">{translate('quality_gates.introduction')}</div> - {uniqDuplicates.length > 0 && ( <Alert variant="warning"> <p>{translate('quality_gates.duplicated_conditions')}</p> @@ -123,43 +173,40 @@ export default class Conditions extends React.PureComponent<Props> { </Alert> )} - {sortedConditions.length ? ( - <table className="data zebra zebra-hover" id="quality-gate-conditions"> - <thead> - <tr> - <th className="nowrap"> - <div className="display-inline-flex-center"> - {translate('quality_gates.conditions.metric')} - <DocTooltip - className="spacer-left" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/metric.md')} - /> - </div> - </th> - <th className="thin nowrap">{translate('quality_gates.conditions.operator')}</th> - <th className="thin nowrap">{translate('quality_gates.conditions.error')}</th> - {canEdit && <th />} - </tr> - </thead> - <tbody> - {sortedConditions.map((condition, index) => ( - <Condition - canEdit={canEdit} - condition={condition} - key={this.getConditionKey(condition, index)} - metric={metrics[condition.metric]} - onRemoveCondition={this.props.onRemoveCondition} - onSaveCondition={this.props.onSaveCondition} - organization={organization} - qualityGate={qualityGate} - /> - ))} - </tbody> - </table> - ) : ( + {sortedConditionsOnNewMetrics.length > 0 && ( + <div className="big-spacer-top"> + <h4>{translate('quality_gates.conditions.new_code.long')}</h4> + + {appState.branchesEnabled && ( + <p className="spacer-top spacer-bottom"> + {translate('quality_gates.conditions.new_code.description')} + </p> + )} + + {this.renderConditionsTable(sortedConditionsOnNewMetrics, 'new')} + </div> + )} + + {sortedConditionsOnOverallMetrics.length > 0 && ( + <div className="big-spacer-top"> + <h4>{translate('quality_gates.conditions.overall_code.long')}</h4> + + {appState.branchesEnabled && ( + <p className="spacer-top spacer-bottom"> + {translate('quality_gates.conditions.overall_code.description')} + </p> + )} + + {this.renderConditionsTable(sortedConditionsOnOverallMetrics, 'overall')} + </div> + )} + + {existingConditions.length === 0 && ( <div className="big-spacer-top">{translate('quality_gates.no_conditions')}</div> )} </div> ); } } + +export default withAppState(Conditions); diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx index 032692b0b2f..1f2bc2bba7e 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx @@ -44,7 +44,7 @@ export default function ListHeader({ canCreate, refreshQualityGates, organizatio /> )}> {({ onClick }) => ( - <Button id="quality-gate-add" onClick={onClick}> + <Button data-test="quality-gates__add" onClick={onClick}> {translate('create')} </Button> )} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx index 2a6f9ad4a00..56300e6d6ac 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/MetricSelect.tsx @@ -20,56 +20,51 @@ import { sortBy } from 'lodash'; import * as React from 'react'; import Select from 'sonar-ui-common/components/controls/Select'; -import { - getLocalizedMetricDomain, - getLocalizedMetricName, - translate -} from 'sonar-ui-common/helpers/l10n'; +import { getLocalizedMetricDomain, translate } from 'sonar-ui-common/helpers/l10n'; +import { getLocalizedMetricNameNoDiffMetric } from '../utils'; interface Props { + metric?: T.Metric; metrics: T.Metric[]; onMetricChange: (metric: T.Metric) => void; } -interface State { - value: number; -} - interface Option { disabled?: boolean; - domain?: string; label: string; - value: number; + value: string; } -export default class MetricSelect extends React.PureComponent<Props, State> { - state = { value: -1 }; - +export default class MetricSelect extends React.PureComponent<Props> { handleChange = (option: Option | null) => { - const value = option ? option.value : -1; - this.setState({ value }); - this.props.onMetricChange(this.props.metrics[value]); + if (option) { + const { metrics } = this.props; + const selectedMetric = metrics.find(metric => metric.key === option.value); + if (selectedMetric) { + this.props.onMetricChange(selectedMetric); + } + } }; render() { - const { metrics } = this.props; + const { metric, metrics } = this.props; - const options: Option[] = sortBy( - metrics.map((metric, index) => ({ - value: index, - label: getLocalizedMetricName(metric), + const options: Array<Option & { domain?: string }> = sortBy( + metrics.map(metric => ({ + value: metric.key, + label: getLocalizedMetricNameNoDiffMetric(metric), domain: metric.domain })), 'domain' ); - // use "disabled" property to emulate optgroups + // Use "disabled" property to emulate optgroups. const optionsWithDomains: Option[] = []; options.forEach((option, index, options) => { const previous = index > 0 ? options[index - 1] : null; if (option.domain && (!previous || previous.domain !== option.domain)) { optionsWithDomains.push({ - value: 0, + value: '<domain>', label: getLocalizedMetricDomain(option.domain), disabled: true }); @@ -84,7 +79,7 @@ export default class MetricSelect extends React.PureComponent<Props, State> { onChange={this.handleChange} options={optionsWithDomains} placeholder={translate('search.search_for_metrics')} - value={this.state.value} + value={metric && metric.key} /> ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx new file mode 100644 index 00000000000..336c33538a1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Condition-test.tsx @@ -0,0 +1,59 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { mockCondition, mockMetric, mockQualityGate } from '../../../../helpers/testMocks'; +import Condition from '../Condition'; + +it('should render correctly', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly with edit rights', () => { + const wrapper = shallowRender({ canEdit: true }); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render the update modal correctly', () => { + const wrapper = shallowRender({ canEdit: true }); + wrapper.instance().handleOpenUpdate(); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render the delete modal correctly', () => { + const wrapper = shallowRender({ canEdit: true }); + wrapper.instance().handleDeleteClick(); + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(props: Partial<Condition['props']> = {}) { + return shallow<Condition>( + <Condition + canEdit={false} + condition={mockCondition()} + metric={mockMetric()} + onRemoveCondition={jest.fn()} + onSaveCondition={jest.fn()} + qualityGate={mockQualityGate()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx index 7b243bf4b42..8b6d1d2fe15 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ConditionModal-test.tsx @@ -19,21 +19,58 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { mockQualityGate } from '../../../../helpers/testMocks'; +import { mockMetric, mockQualityGate } from '../../../../helpers/testMocks'; import ConditionModal from '../ConditionModal'; it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + expect(shallowRender({ metric: mockMetric() })).toMatchSnapshot(); +}); + +it('should correctly handle a metric selection', () => { const wrapper = shallowRender(); + const metric = mockMetric(); + + expect(wrapper.find('MetricSelect').prop('metric')).toBeUndefined(); + + wrapper.instance().handleMetricChange(metric); + expect(wrapper.find('MetricSelect').prop('metric')).toEqual(metric); +}); + +it('should correctly switch scope', () => { + const wrapper = shallowRender({ + metrics: [ + mockMetric({ id: 'new_coverage', key: 'new_coverage', name: 'Coverage on New Code' }), + mockMetric({ + id: 'new_duplication', + key: 'new_duplication', + name: 'Duplication on New Code' + }), + mockMetric(), + mockMetric({ id: 'duplication', key: 'duplication', name: 'Duplication' }) + ] + }); + expect(wrapper).toMatchSnapshot(); + + wrapper.instance().handleScopeChange('overall'); expect(wrapper).toMatchSnapshot(); - wrapper.instance().handleMetricChange({ id: '1', key: 'foo', name: 'Foo', type: 'PERCENT' }); + wrapper.instance().handleScopeChange('new'); expect(wrapper).toMatchSnapshot(); }); function shallowRender(props: Partial<ConditionModal['props']> = {}) { return shallow<ConditionModal>( <ConditionModal - header="a" + header="header" + metrics={[ + mockMetric({ id: 'new_coverage', key: 'new_coverage', name: 'Coverage on New Code' }), + mockMetric({ + id: 'new_duplication', + key: 'new_duplication', + name: 'Duplication on New Code' + }) + ]} onAddCondition={jest.fn()} onClose={jest.fn()} qualityGate={mockQualityGate()} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx new file mode 100644 index 00000000000..98982f434e1 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/Conditions-test.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { mockCondition, mockMetric, mockQualityGate } from '../../../../helpers/testMocks'; +import { Conditions } from '../Conditions'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); +}); + +it('should render correctly with new code conditions', () => { + const wrapper = shallowRender({ + conditions: [ + mockCondition(), + mockCondition({ id: 2, metric: 'duplication' }), + mockCondition({ id: 3, metric: 'new_coverage' }), + mockCondition({ id: 4, metric: 'new_duplication' }) + ] + }); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render correctly for no conditions', () => { + const wrapper = shallowRender({ conditions: [] }); + expect(wrapper).toMatchSnapshot(); +}); + +it('should render the add conditions button and modal', () => { + const wrapper = shallowRender({ canEdit: true }); + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(props: Partial<Conditions['props']> = {}) { + return shallow<Conditions>( + <Conditions + appState={{ branchesEnabled: true }} + canEdit={false} + conditions={[mockCondition(), mockCondition({ id: 2, metric: 'duplication' })]} + metrics={{ + coverage: mockMetric(), + duplication: mockMetric({ id: 'duplication', key: 'duplication', name: 'Duplication' }), + new_coverage: mockMetric({ + id: 'new_coverage', + key: 'new_coverage', + name: 'Coverage on New Code' + }), + new_duplication: mockMetric({ + id: 'new_duplication', + key: 'new_duplication', + name: 'Duplication on New Code' + }) + }} + onAddCondition={jest.fn()} + onRemoveCondition={jest.fn()} + onSaveCondition={jest.fn()} + qualityGate={mockQualityGate()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx new file mode 100644 index 00000000000..651b2260124 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/ListHeader-test.tsx @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 ListHeader from '../ListHeader'; + +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); + const wrapper = shallowRender({ canCreate: true }); + expect(wrapper.find('ModalButton').exists()).toBe(true); + expect(wrapper.find('ModalButton').dive()).toMatchSnapshot(); +}); + +function shallowRender(props = {}) { + return shallow(<ListHeader canCreate={false} refreshQualityGates={jest.fn()} {...props} />); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx index 6210a5adc43..884627e7db1 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/MetricSelect-test.tsx @@ -19,22 +19,24 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; +import { mockMetric } from '../../../../helpers/testMocks'; import MetricSelect from '../MetricSelect'; it('should render correctly', () => { - expect( - shallow( - <MetricSelect - metrics={[ - { - id: '1', - key: '1', - name: 'metric 1', - type: 'test' - } - ]} - onMetricChange={jest.fn()} - /> - ) - ).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); + +it('should correctly handle change', () => { + const onMetricChange = jest.fn(); + const metric = mockMetric(); + const metrics = [mockMetric({ key: 'duplication' }), metric]; + const wrapper = shallowRender({ metrics, onMetricChange }); + wrapper.instance().handleChange({ label: metric.name, value: metric.key }); + expect(onMetricChange).toBeCalledWith(metric); +}); + +function shallowRender(props: Partial<MetricSelect['props']> = {}) { + return shallow<MetricSelect>( + <MetricSelect metrics={[mockMetric()]} onMetricChange={jest.fn()} {...props} /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap new file mode 100644 index 00000000000..cd7a503cf00 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Condition-test.tsx.snap @@ -0,0 +1,189 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<tr> + <td + className="text-middle" + > + Coverage + </td> + <td + className="text-middle nowrap" + > + <span + className="note" + > + quality_gates.operator.LT + </span> + </td> + <td + className="text-middle nowrap" + > + 10.0% + </td> +</tr> +`; + +exports[`should render correctly with edit rights 1`] = ` +<tr> + <td + className="text-middle" + > + Coverage + </td> + <td + className="text-middle nowrap" + > + <span + className="note" + > + quality_gates.operator.LT + </span> + </td> + <td + className="text-middle nowrap" + > + 10.0% + </td> + <td + className="text-center thin" + > + <EditButton + data-test="quality-gates__condition-update" + onClick={[Function]} + /> + </td> + <td + className="text-center thin" + > + <DeleteButton + data-test="quality-gates__condition-delete" + onClick={[Function]} + /> + </td> +</tr> +`; + +exports[`should render the delete modal correctly 1`] = ` +<tr> + <td + className="text-middle" + > + Coverage + </td> + <td + className="text-middle nowrap" + > + <span + className="note" + > + quality_gates.operator.LT + </span> + </td> + <td + className="text-middle nowrap" + > + 10.0% + </td> + <td + className="text-center thin" + > + <EditButton + data-test="quality-gates__condition-update" + onClick={[Function]} + /> + </td> + <td + className="text-center thin" + > + <DeleteButton + data-test="quality-gates__condition-delete" + onClick={[Function]} + /> + </td> + <ConfirmModal + confirmButtonText="delete" + confirmData={ + Object { + "error": "10", + "id": 1, + "metric": "coverage", + "op": "LT", + } + } + header="quality_gates.delete_condition" + isDestructive={true} + onClose={[Function]} + onConfirm={[Function]} + > + quality_gates.delete_condition.confirm.message.Coverage + </ConfirmModal> +</tr> +`; + +exports[`should render the update modal correctly 1`] = ` +<tr> + <td + className="text-middle" + > + Coverage + </td> + <td + className="text-middle nowrap" + > + <span + className="note" + > + quality_gates.operator.LT + </span> + </td> + <td + className="text-middle nowrap" + > + 10.0% + </td> + <td + className="text-center thin" + > + <EditButton + data-test="quality-gates__condition-update" + onClick={[Function]} + /> + </td> + <td + className="text-center thin" + > + <DeleteButton + data-test="quality-gates__condition-delete" + onClick={[Function]} + /> + </td> + <ConditionModal + condition={ + Object { + "error": "10", + "id": 1, + "metric": "coverage", + "op": "LT", + } + } + header="quality_gates.update_condition" + metric={ + Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + onAddCondition={[Function]} + onClose={[Function]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> +</tr> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap index ab32f89c02b..711a500cdf5 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ConditionModal-test.tsx.snap @@ -1,31 +1,278 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should correctly switch scope 1`] = ` +<ConfirmModal + confirmButtonText="header" + confirmDisable={true} + header="header" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field display-flex-center" + > + <Radio + checked={true} + onCheck={[Function]} + value="new" + > + <span + data-test="quality-gates__condition-scope-new" + > + quality_gates.conditions.new_code + </span> + </Radio> + <Radio + checked={false} + className="big-spacer-left" + onCheck={[Function]} + value="overall" + > + <span + data-test="quality-gates__condition-scope-overall" + > + quality_gates.conditions.overall_code + </span> + </Radio> + </div> + <div + className="modal-field" + > + <label + htmlFor="condition-metric" + > + quality_gates.conditions.fails_when + </label> + <MetricSelect + metrics={ + Array [ + Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "Coverage on New Code", + "type": "PERCENT", + }, + Object { + "id": "new_duplication", + "key": "new_duplication", + "name": "Duplication on New Code", + "type": "PERCENT", + }, + ] + } + onMetricChange={[Function]} + /> + </div> +</ConfirmModal> +`; + +exports[`should correctly switch scope 2`] = ` +<ConfirmModal + confirmButtonText="header" + confirmDisable={true} + header="header" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field display-flex-center" + > + <Radio + checked={false} + onCheck={[Function]} + value="new" + > + <span + data-test="quality-gates__condition-scope-new" + > + quality_gates.conditions.new_code + </span> + </Radio> + <Radio + checked={true} + className="big-spacer-left" + onCheck={[Function]} + value="overall" + > + <span + data-test="quality-gates__condition-scope-overall" + > + quality_gates.conditions.overall_code + </span> + </Radio> + </div> + <div + className="modal-field" + > + <label + htmlFor="condition-metric" + > + quality_gates.conditions.fails_when + </label> + <MetricSelect + metrics={ + Array [ + Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + }, + Object { + "id": "duplication", + "key": "duplication", + "name": "Duplication", + "type": "PERCENT", + }, + ] + } + onMetricChange={[Function]} + /> + </div> +</ConfirmModal> +`; + +exports[`should correctly switch scope 3`] = ` +<ConfirmModal + confirmButtonText="header" + confirmDisable={true} + header="header" + onClose={[MockFunction]} + onConfirm={[Function]} + size="small" +> + <div + className="modal-field display-flex-center" + > + <Radio + checked={true} + onCheck={[Function]} + value="new" + > + <span + data-test="quality-gates__condition-scope-new" + > + quality_gates.conditions.new_code + </span> + </Radio> + <Radio + checked={false} + className="big-spacer-left" + onCheck={[Function]} + value="overall" + > + <span + data-test="quality-gates__condition-scope-overall" + > + quality_gates.conditions.overall_code + </span> + </Radio> + </div> + <div + className="modal-field" + > + <label + htmlFor="condition-metric" + > + quality_gates.conditions.fails_when + </label> + <MetricSelect + metrics={ + Array [ + Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "Coverage on New Code", + "type": "PERCENT", + }, + Object { + "id": "new_duplication", + "key": "new_duplication", + "name": "Duplication on New Code", + "type": "PERCENT", + }, + ] + } + onMetricChange={[Function]} + /> + </div> +</ConfirmModal> +`; + exports[`should render correctly 1`] = ` <ConfirmModal - confirmButtonText="a" + confirmButtonText="header" confirmDisable={true} - header="a" + header="header" onClose={[MockFunction]} onConfirm={[Function]} size="small" > <div + className="modal-field display-flex-center" + > + <Radio + checked={true} + onCheck={[Function]} + value="new" + > + <span + data-test="quality-gates__condition-scope-new" + > + quality_gates.conditions.new_code + </span> + </Radio> + <Radio + checked={false} + className="big-spacer-left" + onCheck={[Function]} + value="overall" + > + <span + data-test="quality-gates__condition-scope-overall" + > + quality_gates.conditions.overall_code + </span> + </Radio> + </div> + <div className="modal-field" > <label htmlFor="condition-metric" > - quality_gates.conditions.metric + quality_gates.conditions.fails_when </label> + <MetricSelect + metrics={ + Array [ + Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "Coverage on New Code", + "type": "PERCENT", + }, + Object { + "id": "new_duplication", + "key": "new_duplication", + "name": "Duplication on New Code", + "type": "PERCENT", + }, + ] + } + onMetricChange={[Function]} + /> </div> </ConfirmModal> `; exports[`should render correctly 2`] = ` <ConfirmModal - confirmButtonText="a" + confirmButtonText="header" confirmDisable={false} - header="a" + header="header" onClose={[MockFunction]} onConfirm={[Function]} size="small" @@ -36,8 +283,40 @@ exports[`should render correctly 2`] = ` <label htmlFor="condition-metric" > - quality_gates.conditions.metric + quality_gates.conditions.fails_when </label> + <MetricSelect + metric={ + Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + metrics={ + Array [ + Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "Coverage on New Code", + "type": "PERCENT", + }, + Object { + "id": "new_duplication", + "key": "new_duplication", + "name": "Duplication on New Code", + "type": "PERCENT", + }, + ] + } + onMetricChange={[Function]} + /> + <span + className="note" + > + Coverage + </span> </div> <div className="modal-field display-inline-block" @@ -50,9 +329,9 @@ exports[`should render correctly 2`] = ` <ConditionOperator metric={ Object { - "id": "1", - "key": "foo", - "name": "Foo", + "id": "coverage", + "key": "coverage", + "name": "Coverage", "type": "PERCENT", } } @@ -65,14 +344,14 @@ exports[`should render correctly 2`] = ` <label htmlFor="condition-threshold" > - quality_gates.conditions.error + quality_gates.conditions.value </label> <ThresholdInput metric={ Object { - "id": "1", - "key": "foo", - "name": "Foo", + "id": "coverage", + "key": "coverage", + "name": "Coverage", "type": "PERCENT", } } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap new file mode 100644 index 00000000000..d3db47fc642 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap @@ -0,0 +1,493 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div + className="quality-gate-section" +> + <header + className="display-flex-center spacer-bottom" + > + <h3> + quality_gates.conditions + </h3> + <DocTooltip + className="spacer-left" + doc={Promise {}} + /> + </header> + <div + className="big-spacer-top" + > + <h4> + quality_gates.conditions.overall_code.long + </h4> + <p + className="spacer-top spacer-bottom" + > + quality_gates.conditions.overall_code.description + </p> + <table + className="data zebra" + data-test="quality-gates__conditions-overall" + > + <thead> + <tr> + <th + className="nowrap" + style={ + Object { + "width": 300, + } + } + > + quality_gates.conditions.metric + </th> + <th + className="nowrap" + > + quality_gates.conditions.operator + </th> + <th + className="nowrap" + > + quality_gates.conditions.value + </th> + </tr> + </thead> + <tbody> + <Condition + canEdit={false} + condition={ + Object { + "error": "10", + "id": 1, + "metric": "coverage", + "op": "LT", + } + } + key="1" + metric={ + Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + <Condition + canEdit={false} + condition={ + Object { + "error": "10", + "id": 2, + "metric": "duplication", + "op": "LT", + } + } + key="2" + metric={ + Object { + "id": "duplication", + "key": "duplication", + "name": "Duplication", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + </tbody> + </table> + </div> +</div> +`; + +exports[`should render correctly for no conditions 1`] = ` +<div + className="quality-gate-section" +> + <header + className="display-flex-center spacer-bottom" + > + <h3> + quality_gates.conditions + </h3> + <DocTooltip + className="spacer-left" + doc={Promise {}} + /> + </header> + <div + className="big-spacer-top" + > + quality_gates.no_conditions + </div> +</div> +`; + +exports[`should render correctly with new code conditions 1`] = ` +<div + className="quality-gate-section" +> + <header + className="display-flex-center spacer-bottom" + > + <h3> + quality_gates.conditions + </h3> + <DocTooltip + className="spacer-left" + doc={Promise {}} + /> + </header> + <div + className="big-spacer-top" + > + <h4> + quality_gates.conditions.new_code.long + </h4> + <p + className="spacer-top spacer-bottom" + > + quality_gates.conditions.new_code.description + </p> + <table + className="data zebra" + data-test="quality-gates__conditions-new" + > + <thead> + <tr> + <th + className="nowrap" + style={ + Object { + "width": 300, + } + } + > + quality_gates.conditions.metric + </th> + <th + className="nowrap" + > + quality_gates.conditions.operator + </th> + <th + className="nowrap" + > + quality_gates.conditions.value + </th> + </tr> + </thead> + <tbody> + <Condition + canEdit={false} + condition={ + Object { + "error": "10", + "id": 3, + "metric": "new_coverage", + "op": "LT", + } + } + key="3" + metric={ + Object { + "id": "new_coverage", + "key": "new_coverage", + "name": "Coverage on New Code", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + <Condition + canEdit={false} + condition={ + Object { + "error": "10", + "id": 4, + "metric": "new_duplication", + "op": "LT", + } + } + key="4" + metric={ + Object { + "id": "new_duplication", + "key": "new_duplication", + "name": "Duplication on New Code", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + </tbody> + </table> + </div> + <div + className="big-spacer-top" + > + <h4> + quality_gates.conditions.overall_code.long + </h4> + <p + className="spacer-top spacer-bottom" + > + quality_gates.conditions.overall_code.description + </p> + <table + className="data zebra" + data-test="quality-gates__conditions-overall" + > + <thead> + <tr> + <th + className="nowrap" + style={ + Object { + "width": 300, + } + } + > + quality_gates.conditions.metric + </th> + <th + className="nowrap" + > + quality_gates.conditions.operator + </th> + <th + className="nowrap" + > + quality_gates.conditions.value + </th> + </tr> + </thead> + <tbody> + <Condition + canEdit={false} + condition={ + Object { + "error": "10", + "id": 1, + "metric": "coverage", + "op": "LT", + } + } + key="1" + metric={ + Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + <Condition + canEdit={false} + condition={ + Object { + "error": "10", + "id": 2, + "metric": "duplication", + "op": "LT", + } + } + key="2" + metric={ + Object { + "id": "duplication", + "key": "duplication", + "name": "Duplication", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + </tbody> + </table> + </div> +</div> +`; + +exports[`should render the add conditions button and modal 1`] = ` +<div + className="quality-gate-section" +> + <div + className="pull-right" + > + <ModalButton + modal={[Function]} + > + <Component /> + </ModalButton> + </div> + <header + className="display-flex-center spacer-bottom" + > + <h3> + quality_gates.conditions + </h3> + <DocTooltip + className="spacer-left" + doc={Promise {}} + /> + </header> + <div + className="big-spacer-top" + > + <h4> + quality_gates.conditions.overall_code.long + </h4> + <p + className="spacer-top spacer-bottom" + > + quality_gates.conditions.overall_code.description + </p> + <table + className="data zebra" + data-test="quality-gates__conditions-overall" + > + <thead> + <tr> + <th + className="nowrap" + style={ + Object { + "width": 300, + } + } + > + quality_gates.conditions.metric + </th> + <th + className="nowrap" + > + quality_gates.conditions.operator + </th> + <th + className="nowrap" + > + quality_gates.conditions.value + </th> + <th + className="thin" + > + edit + </th> + <th + className="thin" + > + delete + </th> + </tr> + </thead> + <tbody> + <Condition + canEdit={true} + condition={ + Object { + "error": "10", + "id": 1, + "metric": "coverage", + "op": "LT", + } + } + key="1" + metric={ + Object { + "id": "coverage", + "key": "coverage", + "name": "Coverage", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + <Condition + canEdit={true} + condition={ + Object { + "error": "10", + "id": 2, + "metric": "duplication", + "op": "LT", + } + } + key="2" + metric={ + Object { + "id": "duplication", + "key": "duplication", + "name": "Duplication", + "type": "PERCENT", + } + } + onRemoveCondition={[MockFunction]} + onSaveCondition={[MockFunction]} + qualityGate={ + Object { + "id": 1, + "name": "qualitygate", + } + } + /> + </tbody> + </table> + </div> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap new file mode 100644 index 00000000000..f12ac9a8ce6 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<header + className="page-header" +> + <div + className="display-flex-center" + > + <h1 + className="page-title" + > + quality_gates.page + </h1> + <DocTooltip + className="spacer-left" + doc={Promise {}} + /> + </div> +</header> +`; + +exports[`should render correctly 2`] = ` +<Fragment> + <Button + data-test="quality-gates__add" + onClick={[Function]} + > + create + </Button> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap index b03dceb54a2..a387d03d4ed 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/MetricSelect-test.tsx.snap @@ -9,12 +9,11 @@ exports[`should render correctly 1`] = ` Array [ Object { "domain": undefined, - "label": "metric 1", - "value": 0, + "label": "Coverage", + "value": "coverage", }, ] } placeholder="search.search_for_metrics" - value={-1} /> `; |