@@ -19,11 +19,14 @@ | |||
*/ | |||
import { cloneDeep } from 'lodash'; | |||
import { RequestData } from '../../helpers/request'; | |||
import { mockQualityProfile } from '../../helpers/testMocks'; | |||
import { mockCompareResult, mockQualityProfile, mockRuleDetails } from '../../helpers/testMocks'; | |||
import { SearchRulesResponse } from '../../types/coding-rules'; | |||
import { Dict, Paging, ProfileInheritanceDetails } from '../../types/types'; | |||
import { Dict, Paging, ProfileInheritanceDetails, RuleDetails } from '../../types/types'; | |||
import { | |||
activateRule, | |||
changeProfileParent, | |||
compareProfiles, | |||
CompareResponse, | |||
copyProfile, | |||
createQualityProfile, | |||
getImporters, | |||
@@ -35,18 +38,20 @@ import { | |||
SearchQualityProfilesParameters, | |||
SearchQualityProfilesResponse, | |||
} from '../quality-profiles'; | |||
import { searchRules } from '../rules'; | |||
import { getRuleDetails, searchRules } from '../rules'; | |||
export default class QualityProfilesServiceMock { | |||
isAdmin = false; | |||
listQualityProfile: Profile[] = []; | |||
languageMapping: Dict<Partial<Profile>> = { | |||
c: { language: 'c', languageName: 'C' }, | |||
}; | |||
comparisonResult: CompareResponse = mockCompareResult(); | |||
constructor() { | |||
this.resetQualityProfile(); | |||
this.resetComparisonResult(); | |||
(searchQualityProfiles as jest.Mock).mockImplementation(this.handleSearchQualityProfiles); | |||
(createQualityProfile as jest.Mock).mockImplementation(this.handleCreateQualityProfile); | |||
@@ -56,6 +61,9 @@ export default class QualityProfilesServiceMock { | |||
(copyProfile as jest.Mock).mockImplementation(this.handleCopyProfile); | |||
(getImporters as jest.Mock).mockImplementation(this.handleGetImporters); | |||
(searchRules as jest.Mock).mockImplementation(this.handleSearchRules); | |||
(compareProfiles as jest.Mock).mockImplementation(this.handleCompareQualityProfiles); | |||
(activateRule as jest.Mock).mockImplementation(this.handleActivateRule); | |||
(getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails); | |||
} | |||
resetQualityProfile() { | |||
@@ -70,12 +78,27 @@ export default class QualityProfilesServiceMock { | |||
mockQualityProfile({ | |||
key: 'java-qp', | |||
language: 'java', | |||
languageName: 'Java', | |||
name: 'java quality profile', | |||
activeDeprecatedRuleCount: 0, | |||
}), | |||
mockQualityProfile({ | |||
key: 'java-qp-1', | |||
language: 'java', | |||
languageName: 'Java', | |||
name: 'java quality profile #2', | |||
activeDeprecatedRuleCount: 1, | |||
actions: { | |||
edit: true, | |||
}, | |||
}), | |||
]; | |||
} | |||
resetComparisonResult() { | |||
this.comparisonResult = mockCompareResult(); | |||
} | |||
handleGetImporters = () => { | |||
return this.reply([]); | |||
}; | |||
@@ -201,7 +224,7 @@ export default class QualityProfilesServiceMock { | |||
profiles = profiles.filter((p) => p.language === language); | |||
} | |||
if (this.isAdmin) { | |||
profiles = profiles.map((p) => ({ ...p, actions: { copy: true } })); | |||
profiles = profiles.map((p) => ({ ...p, actions: { ...p.actions, copy: true } })); | |||
} | |||
return this.reply({ | |||
@@ -210,6 +233,46 @@ export default class QualityProfilesServiceMock { | |||
}); | |||
}; | |||
handleActivateRule = (data: { | |||
key: string; | |||
params?: Dict<string>; | |||
reset?: boolean; | |||
rule: string; | |||
severity?: string; | |||
}): Promise<undefined> => { | |||
const profile = this.listQualityProfile.find((profile) => profile.key === data.key) as Profile; | |||
const keyFilter = profile.name === this.comparisonResult.left.name ? 'inRight' : 'inLeft'; | |||
this.comparisonResult[keyFilter] = this.comparisonResult[keyFilter].filter( | |||
({ key }) => key !== data.rule | |||
); | |||
return this.reply(undefined); | |||
}; | |||
handleCompareQualityProfiles = (leftKey: string, rightKey: string): Promise<CompareResponse> => { | |||
const comparedProfiles = this.listQualityProfile.reduce((profiles, profile) => { | |||
if (profile.key === leftKey || profile.key === rightKey) { | |||
profiles.push(profile); | |||
} | |||
return profiles; | |||
}, [] as Profile[]); | |||
const [leftName, rightName] = comparedProfiles.map((profile) => profile.name); | |||
this.comparisonResult.left = { name: leftName }; | |||
this.comparisonResult.right = { name: rightName }; | |||
return this.reply(this.comparisonResult); | |||
}; | |||
handleGetRuleDetails = (params: { key: string }): Promise<{ rule: RuleDetails }> => { | |||
return this.reply({ | |||
rule: mockRuleDetails({ | |||
key: params.key, | |||
}), | |||
}); | |||
}; | |||
setAdmin() { | |||
this.isAdmin = true; | |||
} | |||
@@ -217,6 +280,7 @@ export default class QualityProfilesServiceMock { | |||
reset() { | |||
this.isAdmin = false; | |||
this.resetQualityProfile(); | |||
this.resetComparisonResult(); | |||
} | |||
reply<T>(response: T): Promise<T> { |
@@ -39,10 +39,10 @@ const ui = { | |||
cQualityProfileName: 'c quality profile', | |||
newCQualityProfileName: 'New c quality profile', | |||
newCQualityProfileNameFromCreateButton: 'New c quality profile from create', | |||
actionOnCQualityProfile: byRole('button', { | |||
name: 'quality_profiles.actions.c quality profile.C', | |||
}), | |||
profileActions: (name: string, language: string) => | |||
byRole('button', { | |||
name: `quality_profiles.actions.${name}.${language}`, | |||
}), | |||
extendButton: byRole('button', { | |||
name: 'extend', | |||
}), | |||
@@ -50,6 +50,9 @@ const ui = { | |||
name: 'copy', | |||
}), | |||
createButton: byRole('button', { name: 'create' }), | |||
compareButton: byRole('link', { name: 'compare' }), | |||
compareDropdown: byRole('textbox', { name: 'quality_profiles.compare_with' }), | |||
changelogLink: byRole('link', { name: 'changelog' }), | |||
popup: byRole('dialog'), | |||
copyRadio: byRole('radio', { | |||
name: 'quality_profiles.creation_from_copy quality_profiles.creation_from_copy_description_1 quality_profiles.creation_from_copy_description_2', | |||
@@ -57,6 +60,11 @@ const ui = { | |||
blankRadio: byRole('radio', { | |||
name: 'quality_profiles.creation_from_blank quality_profiles.creation_from_blank_description', | |||
}), | |||
activeRuleButton: (profileName: string) => | |||
byRole('button', { | |||
name: `quality_profiles.comparison.activate_rule.${profileName}`, | |||
}), | |||
activateConfirmButton: byRole('button', { name: 'coding_rules.activate' }), | |||
namePropupInput: byRole('textbox', { name: 'quality_profiles.new_name field_required' }), | |||
filterByLang: byRole('textbox', { name: 'quality_profiles.filter_by:' }), | |||
listLinkCQualityProfile: byRole('link', { name: 'c quality profile' }), | |||
@@ -74,6 +82,12 @@ const ui = { | |||
name: 'quality_profiles.creation.choose_copy_quality_profile field_required', | |||
}), | |||
nameCreatePopupInput: byRole('textbox', { name: 'name field_required' }), | |||
comparisonDiffTableHeading: (rulesQuantity: number, profileName: string) => | |||
byRole('heading', { name: `quality_profiles.x_rules_only_in.${rulesQuantity} ${profileName}` }), | |||
comparisonModifiedTableHeading: (rulesQuantity: number) => | |||
byRole('heading', { | |||
name: `quality_profiles.x_rules_have_different_configuration.${rulesQuantity}`, | |||
}), | |||
}; | |||
it('should list Quality Profiles and filter by language', async () => { | |||
@@ -103,7 +117,7 @@ it('should be able to extend Quality Profile', async () => { | |||
serviceMock.setAdmin(); | |||
renderQualityProfiles(); | |||
await user.click(await ui.actionOnCQualityProfile.find()); | |||
await user.click(await ui.profileActions('c quality profile', 'C').find()); | |||
await user.click(ui.extendButton.get()); | |||
await user.clear(ui.namePropupInput.get()); | |||
@@ -129,7 +143,7 @@ it('should be able to copy Quality Profile', async () => { | |||
serviceMock.setAdmin(); | |||
renderQualityProfiles(); | |||
await user.click(await ui.actionOnCQualityProfile.find()); | |||
await user.click(await ui.profileActions('c quality profile', 'C').find()); | |||
await user.click(ui.copyButton.get()); | |||
await user.clear(ui.namePropupInput.get()); | |||
@@ -166,6 +180,38 @@ it('should be able to create blank Quality Profile', async () => { | |||
expect(await ui.listLinkNewCQualityProfile.find()).toBeInTheDocument(); | |||
}); | |||
it('should be able to compare profiles', async () => { | |||
// From the list page | |||
const user = userEvent.setup(); | |||
serviceMock.setAdmin(); | |||
renderQualityProfiles(); | |||
// For language with 1 profle we should not see compare action | |||
await user.click(await ui.profileActions('c quality profile', 'C').find()); | |||
expect(ui.compareButton.query()).not.toBeInTheDocument(); | |||
await user.click(ui.profileActions('java quality profile', 'Java').get()); | |||
expect(ui.compareButton.get()).toBeInTheDocument(); | |||
await user.click(ui.compareButton.get()); | |||
expect(ui.compareDropdown.get()).toBeInTheDocument(); | |||
expect(ui.profileActions('java quality profile', 'Java').query()).not.toBeInTheDocument(); | |||
expect(ui.changelogLink.query()).not.toBeInTheDocument(); | |||
await selectEvent.select(ui.compareDropdown.get(), 'java quality profile #2'); | |||
expect(ui.comparisonDiffTableHeading(1, 'java quality profile').get()).toBeInTheDocument(); | |||
expect(ui.comparisonDiffTableHeading(1, 'java quality profile #2').get()).toBeInTheDocument(); | |||
expect(ui.comparisonModifiedTableHeading(1).query()).toBeInTheDocument(); | |||
// java quality profile is not editable | |||
expect(ui.activeRuleButton('java quality profile').query()).not.toBeInTheDocument(); | |||
await user.click(ui.activeRuleButton('java quality profile #2').get()); | |||
expect(ui.popup.get()).toBeInTheDocument(); | |||
await user.click(ui.activateConfirmButton.get()); | |||
expect(ui.comparisonDiffTableHeading(1, 'java quality profile').query()).not.toBeInTheDocument(); | |||
}); | |||
function renderQualityProfiles() { | |||
renderAppRoutes('profiles', routes, { | |||
languages: { |
@@ -21,8 +21,9 @@ import * as React from 'react'; | |||
import { Profile } from '../../../api/quality-profiles'; | |||
import { getRuleDetails } from '../../../api/rules'; | |||
import { Button } from '../../../components/controls/buttons'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import DeferredSpinner from '../../../components/ui/DeferredSpinner'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { RuleDetails } from '../../../types/types'; | |||
import ActivationFormModal from '../../coding-rules/components/ActivationFormModal'; | |||
@@ -83,9 +84,24 @@ export default class ComparisonResultActivation extends React.PureComponent<Prop | |||
return ( | |||
<DeferredSpinner loading={this.state.state === 'opening'}> | |||
<Button disabled={this.state.state !== 'closed'} onClick={this.handleButtonClick}> | |||
{this.props.children} | |||
</Button> | |||
<Tooltip | |||
placement="bottom" | |||
overlay={translateWithParameters( | |||
'quality_profiles.comparison.activate_rule', | |||
profile.name | |||
)} | |||
> | |||
<Button | |||
disabled={this.state.state !== 'closed'} | |||
aria-label={translateWithParameters( | |||
'quality_profiles.comparison.activate_rule', | |||
profile.name | |||
)} | |||
onClick={this.handleButtonClick} | |||
> | |||
{this.props.children} | |||
</Button> | |||
</Tooltip> | |||
{this.isOpen(this.state) && ( | |||
<ActivationFormModal |
@@ -1,86 +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 { mockReactSelectOptionProps } from '../../../../helpers/mocks/react-select'; | |||
import Select from '../../../../components/controls/Select'; | |||
import { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import ComparisonForm from '../ComparisonForm'; | |||
it('should render Select with right options', () => { | |||
const output = shallowRender().find(Select); | |||
expect(output.length).toBe(1); | |||
expect(output.prop('value')).toEqual([ | |||
{ isDefault: true, value: 'another', label: 'another name' }, | |||
]); | |||
expect(output.prop('options')).toEqual([ | |||
{ isDefault: true, value: 'another', label: 'another name' }, | |||
]); | |||
}); | |||
it('should render option correctly', () => { | |||
const wrapper = shallowRender(); | |||
const mockOptions = [ | |||
{ | |||
value: 'val', | |||
label: 'label', | |||
isDefault: undefined, | |||
}, | |||
]; | |||
const OptionRenderer = wrapper.instance().optionRenderer.bind(null, mockOptions); | |||
expect( | |||
shallow(<OptionRenderer {...mockReactSelectOptionProps({ value: 'test' })} />) | |||
).toMatchSnapshot('option render'); | |||
}); | |||
it('should render value correctly', () => { | |||
const wrapper = shallowRender(); | |||
const mockOptions = [ | |||
{ | |||
value: 'val', | |||
label: 'label', | |||
isDefault: true, | |||
}, | |||
]; | |||
const ValueRenderer = wrapper.instance().singleValueRenderer.bind(null, mockOptions); | |||
expect( | |||
shallow(<ValueRenderer {...mockReactSelectOptionProps({ value: 'test' })} />) | |||
).toMatchSnapshot('value render'); | |||
}); | |||
function shallowRender(overrides: Partial<ComparisonForm['props']> = {}) { | |||
const profile = mockQualityProfile(); | |||
const profiles = [ | |||
profile, | |||
mockQualityProfile({ key: 'another', name: 'another name', isDefault: true }), | |||
mockQualityProfile({ key: 'java', name: 'java', language: 'java' }), | |||
]; | |||
return shallow<ComparisonForm>( | |||
<ComparisonForm | |||
onCompare={() => true} | |||
profile={profile} | |||
profiles={profiles} | |||
withKey="another" | |||
{...overrides} | |||
/> | |||
); | |||
} |
@@ -1,45 +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 { Profile } from '../../../../api/quality-profiles'; | |||
import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import ComparisonResultActivation from '../ComparisonResultActivation'; | |||
jest.mock('../../../../api/rules', () => ({ | |||
getRuleDetails: jest.fn().mockResolvedValue({ key: 'foo' }), | |||
})); | |||
it('should activate', async () => { | |||
const profile = { actions: { edit: true }, key: 'profile-key' } as Profile; | |||
const wrapper = shallow( | |||
<ComparisonResultActivation onDone={jest.fn()} profile={profile} ruleKey="foo" /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper.find('Button')); | |||
expect(wrapper).toMatchSnapshot(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('ActivationFormModal').prop<Function>('onClose')(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); |
@@ -1,104 +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 { Profile } from '../../../../api/quality-profiles'; | |||
import Link from '../../../../components/common/Link'; | |||
import ComparisonEmpty from '../ComparisonEmpty'; | |||
import ComparisonResults from '../ComparisonResults'; | |||
it('should render ComparisonEmpty', () => { | |||
const output = shallow( | |||
<ComparisonResults | |||
inLeft={[]} | |||
inRight={[]} | |||
left={{ name: 'left' }} | |||
leftProfile={{} as Profile} | |||
modified={[]} | |||
refresh={jest.fn()} | |||
right={{ name: 'right' }} | |||
/> | |||
); | |||
expect(output.is(ComparisonEmpty)).toBe(true); | |||
}); | |||
it('should compare', () => { | |||
const inLeft = [{ key: 'rule1', name: 'rule1', severity: 'BLOCKER' }]; | |||
const inRight = [ | |||
{ key: 'rule2', name: 'rule2', severity: 'CRITICAL' }, | |||
{ key: 'rule3', name: 'rule3', severity: 'MAJOR' }, | |||
]; | |||
const modified = [ | |||
{ | |||
key: 'rule4', | |||
name: 'rule4', | |||
left: { | |||
severity: 'BLOCKER', | |||
params: { foo: 'bar' }, | |||
}, | |||
right: { | |||
severity: 'INFO', | |||
params: { foo: 'qwe' }, | |||
}, | |||
}, | |||
]; | |||
const output = shallow( | |||
<ComparisonResults | |||
inLeft={inLeft} | |||
inRight={inRight} | |||
left={{ name: 'left' }} | |||
leftProfile={{} as Profile} | |||
modified={modified} | |||
refresh={jest.fn()} | |||
right={{ name: 'right' }} | |||
/> | |||
); | |||
const leftDiffs = output.find('.js-comparison-in-left'); | |||
expect(leftDiffs.length).toBe(1); | |||
expect(leftDiffs.find(Link).length).toBe(1); | |||
expect(leftDiffs.find(Link).prop('to')).toHaveProperty('search', '?rule_key=rule1&open=rule1'); | |||
expect(leftDiffs.find(Link).prop('children')).toContain('rule1'); | |||
expect(leftDiffs.find('SeverityIcon').length).toBe(1); | |||
expect(leftDiffs.find('SeverityIcon').prop('severity')).toBe('BLOCKER'); | |||
const rightDiffs = output.find('.js-comparison-in-right'); | |||
expect(rightDiffs.length).toBe(2); | |||
expect(rightDiffs.at(0).find(Link).length).toBe(1); | |||
expect(rightDiffs.at(0).find(Link).prop('to')).toHaveProperty( | |||
'search', | |||
'?rule_key=rule2&open=rule2' | |||
); | |||
expect(rightDiffs.at(0).find(Link).prop('children')).toContain('rule2'); | |||
expect(rightDiffs.at(0).find('SeverityIcon').length).toBe(1); | |||
expect(rightDiffs.at(0).find('SeverityIcon').prop('severity')).toBe('CRITICAL'); | |||
const modifiedDiffs = output.find('.js-comparison-modified'); | |||
expect(modifiedDiffs.length).toBe(1); | |||
expect(modifiedDiffs.find(Link).at(0).prop('to')).toHaveProperty( | |||
'search', | |||
'?rule_key=rule4&open=rule4' | |||
); | |||
expect(modifiedDiffs.find(Link).at(0).prop('children')).toContain('rule4'); | |||
expect(modifiedDiffs.find('SeverityIcon').length).toBe(2); | |||
expect(modifiedDiffs.text()).toContain('bar'); | |||
expect(modifiedDiffs.text()).toContain('qwe'); | |||
}); |
@@ -1,21 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render option correctly: option render 1`] = ` | |||
<Option | |||
data={ | |||
Object { | |||
"value": "test", | |||
} | |||
} | |||
/> | |||
`; | |||
exports[`should render value correctly: value render 1`] = ` | |||
<SingleValue | |||
data={ | |||
Object { | |||
"value": "test", | |||
} | |||
} | |||
/> | |||
`; |
@@ -1,60 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should activate 1`] = ` | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
<Button | |||
disabled={false} | |||
onClick={[Function]} | |||
/> | |||
</DeferredSpinner> | |||
`; | |||
exports[`should activate 2`] = ` | |||
<DeferredSpinner | |||
loading={true} | |||
> | |||
<Button | |||
disabled={true} | |||
onClick={[Function]} | |||
/> | |||
</DeferredSpinner> | |||
`; | |||
exports[`should activate 3`] = ` | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
<Button | |||
disabled={true} | |||
onClick={[Function]} | |||
/> | |||
<ActivationFormModal | |||
modalHeader="coding_rules.activate_in_quality_profile" | |||
onClose={[Function]} | |||
onDone={[MockFunction]} | |||
profiles={ | |||
Array [ | |||
Object { | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"key": "profile-key", | |||
}, | |||
] | |||
} | |||
/> | |||
</DeferredSpinner> | |||
`; | |||
exports[`should activate 4`] = ` | |||
<DeferredSpinner | |||
loading={false} | |||
> | |||
<Button | |||
disabled={false} | |||
onClick={[Function]} | |||
/> | |||
</DeferredSpinner> | |||
`; |
@@ -17,6 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import classNames from 'classnames'; | |||
import { some } from 'lodash'; | |||
import * as React from 'react'; | |||
import { | |||
changeProfileParent, | |||
@@ -36,8 +38,9 @@ import { Router, withRouter } from '../../../components/hoc/withRouter'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../helpers/system'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import { PROFILE_PATH } from '../constants'; | |||
import { Profile, ProfileActionModals } from '../types'; | |||
import { getProfileComparePath, getProfilePath, PROFILE_PATH } from '../utils'; | |||
import { getProfileComparePath, getProfilePath } from '../utils'; | |||
import DeleteProfileForm from './DeleteProfileForm'; | |||
import ProfileModalForm from './ProfileModalForm'; | |||
@@ -45,6 +48,7 @@ interface Props { | |||
className?: string; | |||
profile: Profile; | |||
router: Router; | |||
isComparable: boolean; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
@@ -175,7 +179,7 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const { profile } = this.props; | |||
const { profile, isComparable } = this.props; | |||
const { loading, openModal } = this.state; | |||
const { actions = {} } = profile; | |||
@@ -187,11 +191,12 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
}); | |||
const hasNoActiveRules = profile.activeRuleCount === 0; | |||
const hasAnyAction = some([...Object.values(actions), !profile.isBuiltIn, isComparable]); | |||
return ( | |||
<> | |||
<ActionsDropdown | |||
className={this.props.className} | |||
className={classNames(this.props.className, { invisible: !hasAnyAction })} | |||
label={translateWithParameters( | |||
'quality_profiles.actions', | |||
profile.name, | |||
@@ -217,12 +222,14 @@ export class ProfileActions extends React.PureComponent<Props, State> { | |||
</ActionsDropdownItem> | |||
)} | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={getProfileComparePath(profile.name, profile.language)} | |||
> | |||
{translate('compare')} | |||
</ActionsDropdownItem> | |||
{isComparable && ( | |||
<ActionsDropdownItem | |||
className="it__quality-profiles__compare" | |||
to={getProfileComparePath(profile.name, profile.language)} | |||
> | |||
{translate('compare')} | |||
</ActionsDropdownItem> | |||
)} | |||
{actions.copy && ( | |||
<> |
@@ -48,7 +48,8 @@ export function ProfileContainer(props: QualityProfilesContextProps) { | |||
return profileForKey ? null : <ProfileNotFound />; | |||
} | |||
const profile = profiles.find((p) => p.language === language && p.name === name); | |||
const filteredProfiles = profiles.filter((p) => p.language === language); | |||
const profile = filteredProfiles.find((p) => p.name === name); | |||
if (!profile) { | |||
return <ProfileNotFound />; | |||
@@ -62,7 +63,11 @@ export function ProfileContainer(props: QualityProfilesContextProps) { | |||
return ( | |||
<div id="quality-profile"> | |||
<Helmet defer={false} title={profile.name} /> | |||
<ProfileHeader profile={profile} updateProfiles={props.updateProfiles} /> | |||
<ProfileHeader | |||
profile={profile} | |||
isComparable={filteredProfiles.length > 1} | |||
updateProfiles={props.updateProfiles} | |||
/> | |||
<Outlet context={context} /> | |||
</div> | |||
); |
@@ -20,7 +20,7 @@ | |||
import * as React from 'react'; | |||
import { NavLink } from 'react-router-dom'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { PROFILE_PATH } from '../utils'; | |||
import { PROFILE_PATH } from '../constants'; | |||
export default function ProfileNotFound() { | |||
return ( |
@@ -30,8 +30,8 @@ import { | |||
import { mockQualityProfile, mockRouter } from '../../../../helpers/testMocks'; | |||
import { click, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { queryToSearch } from '../../../../helpers/urls'; | |||
import { PROFILE_PATH } from '../../constants'; | |||
import { ProfileActionModals } from '../../types'; | |||
import { PROFILE_PATH } from '../../utils'; | |||
import DeleteProfileForm from '../DeleteProfileForm'; | |||
import { ProfileActions } from '../ProfileActions'; | |||
import ProfileModalForm from '../ProfileModalForm'; | |||
@@ -331,6 +331,12 @@ it('should not allow to set a profile as the default if the profile has no activ | |||
function shallowRender(props: Partial<ProfileActions['props']> = {}) { | |||
const router = mockRouter(); | |||
return shallow<ProfileActions>( | |||
<ProfileActions profile={PROFILE} router={router} updateProfiles={jest.fn()} {...props} /> | |||
<ProfileActions | |||
isComparable={true} | |||
profile={PROFILE} | |||
router={router} | |||
updateProfiles={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -3,6 +3,7 @@ | |||
exports[`renders correctly: all permissions 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -77,6 +78,7 @@ exports[`renders correctly: all permissions 1`] = ` | |||
exports[`renders correctly: copy modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -127,6 +129,7 @@ exports[`renders correctly: copy modal 1`] = ` | |||
exports[`renders correctly: delete modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -176,6 +179,7 @@ exports[`renders correctly: delete modal 1`] = ` | |||
exports[`renders correctly: edit only 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -220,6 +224,7 @@ exports[`renders correctly: edit only 1`] = ` | |||
exports[`renders correctly: extend modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -270,6 +275,7 @@ exports[`renders correctly: extend modal 1`] = ` | |||
exports[`renders correctly: no permissions 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -297,6 +303,7 @@ exports[`renders correctly: no permissions 1`] = ` | |||
exports[`renders correctly: rename modal 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem | |||
@@ -347,6 +354,7 @@ exports[`renders correctly: rename modal 1`] = ` | |||
exports[`should not allow to set a profile as the default if the profile has no active rules 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="" | |||
label="quality_profiles.actions.name.JavaScript" | |||
> | |||
<ActionsDropdownItem |
@@ -17,20 +17,5 @@ | |||
* 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 { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import ProfileHeader from '../ProfileHeader'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ profile: mockQualityProfile({ isDefault: true }) })).toMatchSnapshot( | |||
'for default profile' | |||
); | |||
}); | |||
function shallowRender(props: Partial<ProfileHeader['props']> = {}) { | |||
return shallow( | |||
<ProfileHeader profile={mockQualityProfile()} updateProfiles={jest.fn()} {...props} /> | |||
); | |||
} | |||
export const PROFILE_PATH = '/profiles'; | |||
export const PROFILE_COMPARE_PATH = `${PROFILE_PATH}/compare`; |
@@ -23,48 +23,55 @@ import { NavLink } from 'react-router-dom'; | |||
import Link from '../../../components/common/Link'; | |||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { useLocation } from '../../../components/hoc/withRouter'; | |||
import DateFromNow from '../../../components/intl/DateFromNow'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { getQualityProfileUrl } from '../../../helpers/urls'; | |||
import BuiltInQualityProfileBadge from '../components/BuiltInQualityProfileBadge'; | |||
import ProfileActions from '../components/ProfileActions'; | |||
import ProfileLink from '../components/ProfileLink'; | |||
import { PROFILE_PATH } from '../constants'; | |||
import { Profile } from '../types'; | |||
import { getProfileChangelogPath, getProfilesForLanguagePath, PROFILE_PATH } from '../utils'; | |||
import { | |||
getProfileChangelogPath, | |||
getProfilesForLanguagePath, | |||
isProfileComparePath, | |||
} from '../utils'; | |||
interface Props { | |||
profile: Profile; | |||
isComparable: boolean; | |||
updateProfiles: () => Promise<void>; | |||
} | |||
export default class ProfileHeader extends React.PureComponent<Props> { | |||
render() { | |||
const { profile } = this.props; | |||
export default function ProfileHeader(props: Props) { | |||
const { profile, isComparable, updateProfiles } = props; | |||
const location = useLocation(); | |||
return ( | |||
<div className="page-header quality-profile-header"> | |||
<div className="note spacer-bottom"> | |||
<NavLink end={true} to={PROFILE_PATH}> | |||
{translate('quality_profiles.page')} | |||
</NavLink> | |||
{' / '} | |||
<Link to={getProfilesForLanguagePath(profile.language)}>{profile.languageName}</Link> | |||
</div> | |||
<h1 className="page-title"> | |||
<ProfileLink language={profile.language} name={profile.name}> | |||
<span>{profile.name}</span> | |||
</ProfileLink> | |||
{profile.isDefault && ( | |||
<Tooltip overlay={translate('quality_profiles.list.default.help')}> | |||
<span className=" spacer-left badge">{translate('default')}</span> | |||
</Tooltip> | |||
)} | |||
{profile.isBuiltIn && ( | |||
<BuiltInQualityProfileBadge className="spacer-left" tooltip={false} /> | |||
)} | |||
</h1> | |||
return ( | |||
<div className="page-header quality-profile-header"> | |||
<div className="note spacer-bottom"> | |||
<NavLink end={true} to={PROFILE_PATH}> | |||
{translate('quality_profiles.page')} | |||
</NavLink> | |||
{' / '} | |||
<Link to={getProfilesForLanguagePath(profile.language)}>{profile.languageName}</Link> | |||
</div> | |||
<h1 className="page-title"> | |||
<ProfileLink language={profile.language} name={profile.name}> | |||
<span>{profile.name}</span> | |||
</ProfileLink> | |||
{profile.isDefault && ( | |||
<Tooltip overlay={translate('quality_profiles.list.default.help')}> | |||
<span className=" spacer-left badge">{translate('default')}</span> | |||
</Tooltip> | |||
)} | |||
{profile.isBuiltIn && ( | |||
<BuiltInQualityProfileBadge className="spacer-left" tooltip={false} /> | |||
)} | |||
</h1> | |||
{!isProfileComparePath(location.pathname) && ( | |||
<div className="pull-right"> | |||
<ul className="list-inline" style={{ lineHeight: '24px' }}> | |||
<li className="small spacer-right"> | |||
@@ -78,47 +85,47 @@ export default class ProfileHeader extends React.PureComponent<Props> { | |||
{translate('changelog')} | |||
</Link> | |||
</li> | |||
<li> | |||
<ProfileActions | |||
className="pull-left" | |||
profile={profile} | |||
updateProfiles={this.props.updateProfiles} | |||
isComparable={isComparable} | |||
updateProfiles={updateProfiles} | |||
/> | |||
</li> | |||
</ul> | |||
</div> | |||
)} | |||
{profile.isBuiltIn && ( | |||
<div className="page-description"> | |||
{translate('quality_profiles.built_in.description')} | |||
</div> | |||
)} | |||
{profile.isBuiltIn && ( | |||
<div className="page-description">{translate('quality_profiles.built_in.description')}</div> | |||
)} | |||
{profile.parentKey && profile.parentName && ( | |||
<div className="page-description"> | |||
<FormattedMessage | |||
defaultMessage={translate('quality_profiles.extend_description')} | |||
id="quality_profiles.extend_description" | |||
values={{ | |||
link: ( | |||
<> | |||
<Link to={getQualityProfileUrl(profile.parentName, profile.language)}> | |||
{profile.parentName} | |||
</Link> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={translateWithParameters( | |||
'quality_profiles.extend_description_help', | |||
profile.parentName | |||
)} | |||
/> | |||
</> | |||
), | |||
}} | |||
/> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} | |||
{profile.parentKey && profile.parentName && ( | |||
<div className="page-description"> | |||
<FormattedMessage | |||
defaultMessage={translate('quality_profiles.extend_description')} | |||
id="quality_profiles.extend_description" | |||
values={{ | |||
link: ( | |||
<> | |||
<Link to={getQualityProfileUrl(profile.parentName, profile.language)}> | |||
{profile.parentName} | |||
</Link> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={translateWithParameters( | |||
'quality_profiles.extend_description_help', | |||
profile.parentName | |||
)} | |||
/> | |||
</> | |||
), | |||
}} | |||
/> | |||
</div> | |||
)} | |||
</div> | |||
); | |||
} |
@@ -1,214 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page-header quality-profile-header" | |||
> | |||
<div | |||
className="note spacer-bottom" | |||
> | |||
<NavLink | |||
end={true} | |||
to="/profiles" | |||
> | |||
quality_profiles.page | |||
</NavLink> | |||
/ | |||
<ForwardRef(Link) | |||
to={ | |||
Object { | |||
"pathname": "/profiles", | |||
"search": "?language=js", | |||
} | |||
} | |||
> | |||
JavaScript | |||
</ForwardRef(Link)> | |||
</div> | |||
<h1 | |||
className="page-title" | |||
> | |||
<ProfileLink | |||
language="js" | |||
name="name" | |||
> | |||
<span> | |||
name | |||
</span> | |||
</ProfileLink> | |||
</h1> | |||
<div | |||
className="pull-right" | |||
> | |||
<ul | |||
className="list-inline" | |||
style={ | |||
Object { | |||
"lineHeight": "24px", | |||
} | |||
} | |||
> | |||
<li | |||
className="small spacer-right" | |||
> | |||
quality_profiles.updated_ | |||
<DateFromNow /> | |||
</li> | |||
<li | |||
className="small big-spacer-right" | |||
> | |||
quality_profiles.used_ | |||
<DateFromNow /> | |||
</li> | |||
<li> | |||
<ForwardRef(Link) | |||
className="button" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/changelog", | |||
"search": "?language=js&name=name", | |||
} | |||
} | |||
> | |||
changelog | |||
</ForwardRef(Link)> | |||
</li> | |||
<li> | |||
<withRouter(ProfileActions) | |||
className="pull-left" | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: for default profile 1`] = ` | |||
<div | |||
className="page-header quality-profile-header" | |||
> | |||
<div | |||
className="note spacer-bottom" | |||
> | |||
<NavLink | |||
end={true} | |||
to="/profiles" | |||
> | |||
quality_profiles.page | |||
</NavLink> | |||
/ | |||
<ForwardRef(Link) | |||
to={ | |||
Object { | |||
"pathname": "/profiles", | |||
"search": "?language=js", | |||
} | |||
} | |||
> | |||
JavaScript | |||
</ForwardRef(Link)> | |||
</div> | |||
<h1 | |||
className="page-title" | |||
> | |||
<ProfileLink | |||
language="js" | |||
name="name" | |||
> | |||
<span> | |||
name | |||
</span> | |||
</ProfileLink> | |||
<Tooltip | |||
overlay="quality_profiles.list.default.help" | |||
> | |||
<span | |||
className=" spacer-left badge" | |||
> | |||
default | |||
</span> | |||
</Tooltip> | |||
</h1> | |||
<div | |||
className="pull-right" | |||
> | |||
<ul | |||
className="list-inline" | |||
style={ | |||
Object { | |||
"lineHeight": "24px", | |||
} | |||
} | |||
> | |||
<li | |||
className="small spacer-right" | |||
> | |||
quality_profiles.updated_ | |||
<DateFromNow /> | |||
</li> | |||
<li | |||
className="small big-spacer-right" | |||
> | |||
quality_profiles.used_ | |||
<DateFromNow /> | |||
</li> | |||
<li> | |||
<ForwardRef(Link) | |||
className="button" | |||
to={ | |||
Object { | |||
"pathname": "/profiles/changelog", | |||
"search": "?language=js&name=name", | |||
} | |||
} | |||
> | |||
changelog | |||
</ForwardRef(Link)> | |||
</li> | |||
<li> | |||
<withRouter(ProfileActions) | |||
className="pull-left" | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": true, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
`; |
@@ -42,6 +42,7 @@ export default class ProfilesList extends React.PureComponent<Props> { | |||
key={profile.key} | |||
profile={profile} | |||
updateProfiles={this.props.updateProfiles} | |||
isComparable={profiles.length > 1} | |||
/> | |||
)); | |||
} |
@@ -21,7 +21,8 @@ import * as React from 'react'; | |||
import Select from '../../../components/controls/Select'; | |||
import { Router, withRouter } from '../../../components/hoc/withRouter'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getProfilesForLanguagePath, PROFILE_PATH } from '../utils'; | |||
import { PROFILE_PATH } from '../constants'; | |||
import { getProfilesForLanguagePath } from '../utils'; | |||
interface Props { | |||
currentFilter?: string; |
@@ -31,10 +31,11 @@ import { Profile } from '../types'; | |||
export interface ProfilesListRowProps { | |||
profile: Profile; | |||
updateProfiles: () => Promise<void>; | |||
isComparable: boolean; | |||
} | |||
export function ProfilesListRow(props: ProfilesListRowProps) { | |||
const { profile } = props; | |||
const { profile, isComparable } = props; | |||
const offset = 25 * (profile.depth - 1); | |||
const activeRulesUrl = getRulesUrl({ | |||
@@ -99,7 +100,11 @@ export function ProfilesListRow(props: ProfilesListRowProps) { | |||
</td> | |||
<td className="quality-profiles-table-actions thin nowrap text-middle text-right"> | |||
<ProfileActions profile={profile} updateProfiles={props.updateProfiles} /> | |||
<ProfileActions | |||
isComparable={isComparable} | |||
profile={profile} | |||
updateProfiles={props.updateProfiles} | |||
/> | |||
</td> | |||
</tr> | |||
); |
@@ -1,45 +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 { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import ProfilesList from '../ProfilesList'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
expect(shallowRender({ language: 'css' })).toMatchSnapshot(); | |||
expect(shallowRender({ language: 'unknown' })).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<ProfilesList['props']> = {}) { | |||
return shallow( | |||
<ProfilesList | |||
languages={[mockLanguage(), mockLanguage({ key: 'js', name: 'JS' })]} | |||
profiles={[ | |||
mockQualityProfile(), | |||
mockQualityProfile({ language: 'css', languageName: 'CSS' }), | |||
]} | |||
updateProfiles={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,46 +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 { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import { ProfilesListRow, ProfilesListRowProps } from '../ProfilesListRow'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ profile: mockQualityProfile({ isBuiltIn: true }) })).toMatchSnapshot( | |||
'built-in profile' | |||
); | |||
expect(shallowRender({ profile: mockQualityProfile({ isDefault: true }) })).toMatchSnapshot( | |||
'default profile' | |||
); | |||
expect( | |||
shallowRender({ profile: mockQualityProfile({ activeDeprecatedRuleCount: 10 }) }) | |||
).toMatchSnapshot('with deprecated rules'); | |||
}); | |||
function shallowRender(props: Partial<ProfilesListRowProps> = {}) { | |||
return shallow( | |||
<ProfilesListRow | |||
profile={mockQualityProfile({ activeDeprecatedRuleCount: 0 })} | |||
updateProfiles={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,288 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div> | |||
<withRouter(ProfilesListHeader) | |||
languages={ | |||
Array [ | |||
Object { | |||
"key": "css", | |||
"name": "CSS", | |||
}, | |||
Object { | |||
"key": "js", | |||
"name": "JS", | |||
}, | |||
] | |||
} | |||
/> | |||
<div | |||
className="boxed-group boxed-group-inner quality-profiles-table" | |||
key="css" | |||
> | |||
<table | |||
className="data zebra zebra-hover" | |||
data-language="css" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
CSS | |||
, | |||
quality_profiles.x_profiles.1 | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.projects | |||
<HelpTooltip | |||
className="table-cell-doc" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.rules | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.updated | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.used | |||
</th> | |||
<th> | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<Memo(ProfilesListRow) | |||
key="key" | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "css", | |||
"languageName": "CSS", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</tbody> | |||
</table> | |||
</div> | |||
<div | |||
className="boxed-group boxed-group-inner quality-profiles-table" | |||
key="js" | |||
> | |||
<table | |||
className="data zebra zebra-hover" | |||
data-language="js" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
JS | |||
, | |||
quality_profiles.x_profiles.1 | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.projects | |||
<HelpTooltip | |||
className="table-cell-doc" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.rules | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.updated | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.used | |||
</th> | |||
<th> | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<Memo(ProfilesListRow) | |||
key="key" | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<div> | |||
<withRouter(ProfilesListHeader) | |||
currentFilter="css" | |||
languages={ | |||
Array [ | |||
Object { | |||
"key": "css", | |||
"name": "CSS", | |||
}, | |||
Object { | |||
"key": "js", | |||
"name": "JS", | |||
}, | |||
] | |||
} | |||
/> | |||
<div | |||
className="boxed-group boxed-group-inner quality-profiles-table" | |||
key="css" | |||
> | |||
<table | |||
className="data zebra zebra-hover" | |||
data-language="css" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
CSS | |||
, | |||
quality_profiles.x_profiles.1 | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.projects | |||
<HelpTooltip | |||
className="table-cell-doc" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.rules | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.updated | |||
</th> | |||
<th | |||
className="text-right nowrap" | |||
> | |||
quality_profiles.list.used | |||
</th> | |||
<th> | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<Memo(ProfilesListRow) | |||
key="key" | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "css", | |||
"languageName": "CSS", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly 3`] = ` | |||
<div> | |||
<withRouter(ProfilesListHeader) | |||
currentFilter="unknown" | |||
languages={ | |||
Array [ | |||
Object { | |||
"key": "css", | |||
"name": "CSS", | |||
}, | |||
Object { | |||
"key": "js", | |||
"name": "JS", | |||
}, | |||
] | |||
} | |||
/> | |||
<Alert | |||
className="spacer-top" | |||
variant="warning" | |||
> | |||
no_results | |||
</Alert> | |||
</div> | |||
`; |
@@ -1,411 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: built-in profile 1`] = ` | |||
<tr | |||
className="quality-profiles-table-row text-middle" | |||
data-key="key" | |||
data-name="name" | |||
> | |||
<td | |||
className="quality-profiles-table-name text-middle" | |||
> | |||
<div | |||
className="display-flex-center" | |||
style={ | |||
Object { | |||
"paddingLeft": 0, | |||
} | |||
} | |||
> | |||
<div> | |||
<ProfileLink | |||
language="js" | |||
name="name" | |||
> | |||
name | |||
</ProfileLink> | |||
</div> | |||
<BuiltInQualityProfileBadge | |||
className="spacer-left" | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-projects thin nowrap text-middle text-right" | |||
> | |||
<span> | |||
3 | |||
</span> | |||
</td> | |||
<td | |||
className="quality-profiles-table-rules thin nowrap text-middle text-right" | |||
> | |||
<div> | |||
<span | |||
className="spacer-right" | |||
> | |||
<Tooltip | |||
overlay="quality_profiles.deprecated_rules" | |||
> | |||
<ForwardRef(Link) | |||
className="badge badge-error" | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true&statuses=DEPRECATED", | |||
} | |||
} | |||
> | |||
2 | |||
</ForwardRef(Link)> | |||
</Tooltip> | |||
</span> | |||
<ForwardRef(Link) | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-actions thin nowrap text-middle text-right" | |||
> | |||
<withRouter(ProfileActions) | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": true, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<tr | |||
className="quality-profiles-table-row text-middle" | |||
data-key="key" | |||
data-name="name" | |||
> | |||
<td | |||
className="quality-profiles-table-name text-middle" | |||
> | |||
<div | |||
className="display-flex-center" | |||
style={ | |||
Object { | |||
"paddingLeft": 0, | |||
} | |||
} | |||
> | |||
<div> | |||
<ProfileLink | |||
language="js" | |||
name="name" | |||
> | |||
name | |||
</ProfileLink> | |||
</div> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-projects thin nowrap text-middle text-right" | |||
> | |||
<span> | |||
3 | |||
</span> | |||
</td> | |||
<td | |||
className="quality-profiles-table-rules thin nowrap text-middle text-right" | |||
> | |||
<div> | |||
<ForwardRef(Link) | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-actions thin nowrap text-middle text-right" | |||
> | |||
<withRouter(ProfileActions) | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 0, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: default profile 1`] = ` | |||
<tr | |||
className="quality-profiles-table-row text-middle" | |||
data-key="key" | |||
data-name="name" | |||
> | |||
<td | |||
className="quality-profiles-table-name text-middle" | |||
> | |||
<div | |||
className="display-flex-center" | |||
style={ | |||
Object { | |||
"paddingLeft": 0, | |||
} | |||
} | |||
> | |||
<div> | |||
<ProfileLink | |||
language="js" | |||
name="name" | |||
> | |||
name | |||
</ProfileLink> | |||
</div> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-projects thin nowrap text-middle text-right" | |||
> | |||
<Tooltip | |||
overlay="quality_profiles.list.default.help" | |||
> | |||
<span | |||
className="badge" | |||
> | |||
default | |||
</span> | |||
</Tooltip> | |||
</td> | |||
<td | |||
className="quality-profiles-table-rules thin nowrap text-middle text-right" | |||
> | |||
<div> | |||
<span | |||
className="spacer-right" | |||
> | |||
<Tooltip | |||
overlay="quality_profiles.deprecated_rules" | |||
> | |||
<ForwardRef(Link) | |||
className="badge badge-error" | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true&statuses=DEPRECATED", | |||
} | |||
} | |||
> | |||
2 | |||
</ForwardRef(Link)> | |||
</Tooltip> | |||
</span> | |||
<ForwardRef(Link) | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-actions thin nowrap text-middle text-right" | |||
> | |||
<withRouter(ProfileActions) | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": true, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly: with deprecated rules 1`] = ` | |||
<tr | |||
className="quality-profiles-table-row text-middle" | |||
data-key="key" | |||
data-name="name" | |||
> | |||
<td | |||
className="quality-profiles-table-name text-middle" | |||
> | |||
<div | |||
className="display-flex-center" | |||
style={ | |||
Object { | |||
"paddingLeft": 0, | |||
} | |||
} | |||
> | |||
<div> | |||
<ProfileLink | |||
language="js" | |||
name="name" | |||
> | |||
name | |||
</ProfileLink> | |||
</div> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-projects thin nowrap text-middle text-right" | |||
> | |||
<span> | |||
3 | |||
</span> | |||
</td> | |||
<td | |||
className="quality-profiles-table-rules thin nowrap text-middle text-right" | |||
> | |||
<div> | |||
<span | |||
className="spacer-right" | |||
> | |||
<Tooltip | |||
overlay="quality_profiles.deprecated_rules" | |||
> | |||
<ForwardRef(Link) | |||
className="badge badge-error" | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true&statuses=DEPRECATED", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</Tooltip> | |||
</span> | |||
<ForwardRef(Link) | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"search": "?qprofile=key&activation=true", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</div> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-date thin nowrap text-middle text-right" | |||
> | |||
<DateFromNow /> | |||
</td> | |||
<td | |||
className="quality-profiles-table-actions thin nowrap text-middle text-right" | |||
> | |||
<withRouter(ProfileActions) | |||
profile={ | |||
Object { | |||
"activeDeprecatedRuleCount": 10, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
updateProfiles={[MockFunction]} | |||
/> | |||
</td> | |||
</tr> | |||
`; |
@@ -22,6 +22,7 @@ import { sortBy } from 'lodash'; | |||
import { Profile as BaseProfile } from '../../api/quality-profiles'; | |||
import { isValidDate, parseDate } from '../../helpers/dates'; | |||
import { queryToSearch } from '../../helpers/urls'; | |||
import { PROFILE_COMPARE_PATH, PROFILE_PATH } from './constants'; | |||
import { Profile } from './types'; | |||
export function sortProfiles(profiles: BaseProfile[]): Profile[] { | |||
@@ -65,8 +66,6 @@ export function isStagnant(profile: Profile): boolean { | |||
return false; | |||
} | |||
export const PROFILE_PATH = '/profiles'; | |||
export const getProfilesForLanguagePath = (language: string) => ({ | |||
pathname: PROFILE_PATH, | |||
search: queryToSearch({ language }), | |||
@@ -83,7 +82,7 @@ export const getProfileComparePath = (name: string, language: string, withKey?: | |||
Object.assign(query, { withKey }); | |||
} | |||
return { | |||
pathname: `${PROFILE_PATH}/compare`, | |||
pathname: PROFILE_COMPARE_PATH, | |||
search: queryToSearch(query), | |||
}; | |||
}; | |||
@@ -107,3 +106,7 @@ export const getProfileChangelogPath = ( | |||
search: queryToSearch(query), | |||
}; | |||
}; | |||
export const isProfileComparePath = (pathname: string): boolean => { | |||
return pathname === PROFILE_COMPARE_PATH; | |||
}; |
@@ -18,6 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { To } from 'react-router-dom'; | |||
import { CompareResponse } from '../api/quality-profiles'; | |||
import { RuleDescriptionSections } from '../apps/coding-rules/rule'; | |||
import { Exporter, Profile } from '../apps/quality-profiles/types'; | |||
import { Location, Router } from '../components/hoc/withRouter'; | |||
@@ -437,6 +438,36 @@ export function mockQualityProfile(overrides: Partial<Profile> = {}): Profile { | |||
}; | |||
} | |||
export function mockCompareResult(overrides: Partial<CompareResponse> = {}): CompareResponse { | |||
return { | |||
left: { name: 'Profile A' }, | |||
right: { name: 'Profile B' }, | |||
inLeft: [ | |||
{ | |||
key: 'java:S4604', | |||
name: 'Rule in left', | |||
severity: 'MINOR', | |||
}, | |||
], | |||
inRight: [ | |||
{ | |||
key: 'java:S5128', | |||
name: 'Rule in right', | |||
severity: 'MAJOR', | |||
}, | |||
], | |||
modified: [ | |||
{ | |||
key: 'java:S1698', | |||
name: '== and != should not be used when equals is overridden', | |||
left: { params: {}, severity: 'MINOR' }, | |||
right: { params: {}, severity: 'CRITICAL' }, | |||
}, | |||
], | |||
...overrides, | |||
}; | |||
} | |||
export function mockQualityProfileInheritance( | |||
overrides: Partial<ProfileInheritanceDetails> = {} | |||
): ProfileInheritanceDetails { |
@@ -1701,6 +1701,7 @@ quality_profile.empty_comparison=The quality profiles are equal. | |||
quality_profiles.activate_more=Activate More | |||
quality_profiles.activate_more.help.built_in=This quality profile is built in, and cannot be updated manually. If you want to activate more rules, create a new profile that inherits from this one and add rules there. | |||
quality_profiles.activate_more_rules=Activate More Rules | |||
quality_profiles.comparison.activate_rule=Activate rule for profile "{0}" | |||
quality_profiles.intro1=Quality profiles are collections of rules to apply during an analysis. | |||
quality_profiles.intro2=For each language there is a default profile. All projects not explicitly assigned to some other profile will be analyzed with the default. Ideally, all projects will use the same profile for a language. | |||
quality_profiles.list.projects=Projects | |||
@@ -1758,7 +1759,6 @@ quality_profiles.actions=Open {0} {1} quality profile actions | |||
#------------------------------------------------------------------------------ | |||
# | |||
# QUALITY GATES |