@@ -0,0 +1,176 @@ | |||
/* | |||
* 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 { cloneDeep, countBy } from 'lodash'; | |||
import { mockQualityProfile, mockRule, mockRuleRepository } from '../../helpers/testMocks'; | |||
import { RuleRepository } from '../../types/coding-rules'; | |||
import { SearchRulesQuery } from '../../types/rules'; | |||
import { Rule } from '../../types/types'; | |||
import { | |||
bulkActivateRules, | |||
bulkDeactivateRules, | |||
Profile, | |||
searchQualityProfiles, | |||
SearchQualityProfilesParameters, | |||
SearchQualityProfilesResponse | |||
} from '../quality-profiles'; | |||
import { getRulesApp, searchRules } from '../rules'; | |||
interface FacetFilter { | |||
languages?: string; | |||
} | |||
const FACET_RULE_MAP: { [key: string]: keyof Rule } = { | |||
languages: 'lang', | |||
types: 'type' | |||
}; | |||
export default class CodingRulesMock { | |||
defaultRules: Rule[] = []; | |||
rules: Rule[] = []; | |||
qualityProfile: Profile[] = []; | |||
repositories: RuleRepository[] = []; | |||
isAdmin = false; | |||
applyWithWarning = false; | |||
constructor() { | |||
this.repositories = [ | |||
mockRuleRepository({ key: 'repo1' }), | |||
mockRuleRepository({ key: 'repo2' }) | |||
]; | |||
this.qualityProfile = [ | |||
mockQualityProfile({ key: 'p1', name: 'QP Foo', language: 'java', languageName: 'Java' }), | |||
mockQualityProfile({ key: 'p2', name: 'QP Bar', language: 'js' }), | |||
mockQualityProfile({ key: 'p3', name: 'QP FooBar', language: 'java', languageName: 'Java' }) | |||
]; | |||
this.defaultRules = [ | |||
mockRule({ | |||
key: 'rule1', | |||
type: 'BUG', | |||
lang: 'java', | |||
langName: 'Java', | |||
name: 'Awsome java rule' | |||
}), | |||
mockRule({ key: 'rule2', name: 'Hot hotspot', type: 'SECURITY_HOTSPOT' }), | |||
mockRule({ key: 'rule3', name: 'Unknown rule' }), | |||
mockRule({ key: 'rule4', type: 'BUG', lang: 'c', langName: 'C', name: 'Awsome C rule' }) | |||
]; | |||
(searchRules as jest.Mock).mockImplementation(this.handleSearchRules); | |||
(searchQualityProfiles as jest.Mock).mockImplementation(this.handleSearchQualityProfiles); | |||
(getRulesApp as jest.Mock).mockImplementation(this.handleGetRulesApp); | |||
(bulkActivateRules as jest.Mock).mockImplementation(this.handleBulkActivateRules); | |||
(bulkDeactivateRules as jest.Mock).mockImplementation(this.handleBulkDeactivateRules); | |||
this.rules = cloneDeep(this.defaultRules); | |||
} | |||
filterFacet({ languages }: FacetFilter) { | |||
let filteredRules = this.rules; | |||
if (languages) { | |||
filteredRules = filteredRules.filter(r => r.lang && languages.includes(r.lang)); | |||
} | |||
return filteredRules; | |||
} | |||
setIsAdmin() { | |||
this.isAdmin = true; | |||
} | |||
activateWithWarning() { | |||
this.applyWithWarning = true; | |||
} | |||
reset() { | |||
this.isAdmin = false; | |||
this.applyWithWarning = false; | |||
this.rules = cloneDeep(this.defaultRules); | |||
} | |||
allRulesName() { | |||
return this.rules.map(r => r.name); | |||
} | |||
allQualityProfile(language: string) { | |||
return this.qualityProfile.filter(qp => qp.language === language); | |||
} | |||
handleSearchRules = ({ facets, languages, p, ps }: SearchRulesQuery) => { | |||
const countFacet = (facets || '').split(',').map((facet: keyof Rule) => { | |||
const facetCount = countBy(this.rules.map(r => r[FACET_RULE_MAP[facet] || facet] as string)); | |||
return { | |||
property: facet, | |||
values: Object.keys(facetCount).map(val => ({ val, count: facetCount[val] })) | |||
}; | |||
}); | |||
const currentPs = ps || 10; | |||
const currentP = p || 1; | |||
const filteredRules = this.filterFacet({ languages }); | |||
const responseRules = filteredRules.slice((currentP - 1) * currentPs, currentP * currentPs); | |||
return this.reply({ | |||
total: filteredRules.length, | |||
p: currentP, | |||
ps: currentPs, | |||
rules: responseRules, | |||
facets: countFacet | |||
}); | |||
}; | |||
handleBulkActivateRules = () => { | |||
if (this.applyWithWarning) { | |||
return this.reply({ | |||
succeeded: this.rules.length - 1, | |||
failed: 1, | |||
errors: [{ msg: 'c rule c:S6069 cannot be activated on cpp profile SonarSource' }] | |||
}); | |||
} | |||
return this.reply({ | |||
succeeded: this.rules.length, | |||
failed: 0, | |||
errors: [] | |||
}); | |||
}; | |||
handleBulkDeactivateRules = () => { | |||
return this.reply({ | |||
succeeded: this.rules.length, | |||
failed: 0 | |||
}); | |||
}; | |||
handleSearchQualityProfiles = ({ language }: SearchQualityProfilesParameters = {}): Promise< | |||
SearchQualityProfilesResponse | |||
> => { | |||
let profiles: Profile[] = this.isAdmin | |||
? this.qualityProfile.map(p => ({ ...p, actions: { edit: true } })) | |||
: this.qualityProfile; | |||
if (language) { | |||
profiles = profiles.filter(p => p.language === language); | |||
} | |||
return this.reply({ profiles }); | |||
}; | |||
handleGetRulesApp = () => { | |||
return this.reply({ canWrite: this.isAdmin, repositories: this.repositories }); | |||
}; | |||
reply<T>(response: T): Promise<T> { | |||
return Promise.resolve(cloneDeep(response)); | |||
} | |||
} |
@@ -20,40 +20,14 @@ | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { getJSON, post, postJSON } from '../helpers/request'; | |||
import { GetRulesAppResponse, SearchRulesResponse } from '../types/coding-rules'; | |||
import { SearchRulesQuery } from '../types/rules'; | |||
import { RuleActivation, RuleDetails } from '../types/types'; | |||
export function getRulesApp(): Promise<GetRulesAppResponse> { | |||
return getJSON('/api/rules/app').catch(throwGlobalError); | |||
} | |||
export function searchRules(data: { | |||
activation?: boolean | string; | |||
active_severities?: string; | |||
asc?: boolean | string; | |||
available_since?: string; | |||
cwe?: string; | |||
f?: string; | |||
facets?: string; | |||
include_external?: boolean | string; | |||
inheritance?: string; | |||
is_template?: boolean | string; | |||
languages?: string; | |||
owaspTop10?: string; | |||
p?: number; | |||
ps?: number; | |||
q?: string; | |||
qprofile?: string; | |||
repositories?: string; | |||
rule_key?: string; | |||
s?: string; | |||
sansTop25?: string; | |||
severities?: string; | |||
sonarsourceSecurity?: string; | |||
statuses?: string; | |||
tags?: string; | |||
template_key?: string; | |||
types?: string; | |||
}): Promise<SearchRulesResponse> { | |||
export function searchRules(data: SearchRulesQuery): Promise<SearchRulesResponse> { | |||
return getJSON('/api/rules/search', data).catch(throwGlobalError); | |||
} | |||
@@ -0,0 +1,197 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { screen, waitFor, within } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import CodingRulesMock from '../../../api/mocks/CodingRulesMock'; | |||
import { mockLoggedInUser } from '../../../helpers/testMocks'; | |||
import { renderApp } from '../../../helpers/testReactTestingUtils'; | |||
import { CurrentUser } from '../../../types/users'; | |||
import routes from '../routes'; | |||
jest.mock('../../../api/rules'); | |||
jest.mock('../../../api/quality-profiles'); | |||
let handler: CodingRulesMock; | |||
beforeAll(() => { | |||
window.scrollTo = jest.fn(); | |||
handler = new CodingRulesMock(); | |||
}); | |||
afterEach(() => handler.reset()); | |||
jest.setTimeout(10_000); | |||
it('should list all rules', async () => { | |||
renderCodingRulesApp(); | |||
await waitFor(() => { | |||
handler | |||
.allRulesName() | |||
.forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument()); | |||
}); | |||
}); | |||
it('should have all type facet', async () => { | |||
renderCodingRulesApp(); | |||
await waitFor(() => { | |||
[ | |||
'issue.type.BUG', | |||
'issue.type.VULNERABILITY', | |||
'issue.type.CODE_SMELL', | |||
'issue.type.SECURITY_HOTSPOT' | |||
].forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument()); | |||
}); | |||
}); | |||
it('select the correct quality profile for bulk change base on language search', async () => { | |||
const user = userEvent.setup(); | |||
handler.setIsAdmin(); | |||
renderCodingRulesApp(mockLoggedInUser()); | |||
const selectQP = handler.allQualityProfile('js')[0]; | |||
await user.click(await screen.findByRole('link', { name: 'JavaScript' })); | |||
await user.click(await screen.findByRole('button', { name: 'bulk_change' })); | |||
await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' })); | |||
const dialog = screen.getByRole('dialog', { | |||
name: 'coding_rules.activate_in_quality_profile (2 coding_rules._rules)' | |||
}); | |||
expect(dialog).toBeInTheDocument(); | |||
const dialogScreen = within(dialog); | |||
expect(dialogScreen.getByText(`${selectQP.name} - ${selectQP.languageName}`)).toBeInTheDocument(); | |||
}); | |||
it('no quality profile for bulk cahnge base on language search', async () => { | |||
const user = userEvent.setup(); | |||
handler.setIsAdmin(); | |||
renderCodingRulesApp(mockLoggedInUser()); | |||
await user.click(await screen.findByRole('link', { name: 'C' })); | |||
await user.click(await screen.findByRole('button', { name: 'bulk_change' })); | |||
await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' })); | |||
const dialog = screen.getByRole('dialog', { | |||
name: 'coding_rules.activate_in_quality_profile (1 coding_rules._rules)' | |||
}); | |||
expect(dialog).toBeInTheDocument(); | |||
const dialogScreen = within(dialog); | |||
await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' })); | |||
expect(dialogScreen.getByText('coding_rules.bulk_change.no_quality_profile')).toBeInTheDocument(); | |||
}); | |||
it('should be able to bulk activate quality profile', async () => { | |||
const user = userEvent.setup(); | |||
handler.setIsAdmin(); | |||
renderCodingRulesApp(mockLoggedInUser()); | |||
const selectQPSuccess = handler.allQualityProfile('java')[0]; | |||
const selectQPWarning = handler.allQualityProfile('java')[1]; | |||
await user.click(await screen.findByRole('button', { name: 'bulk_change' })); | |||
await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' })); | |||
const dialog = screen.getByRole('dialog', { | |||
name: 'coding_rules.activate_in_quality_profile (4 coding_rules._rules)' | |||
}); | |||
expect(dialog).toBeInTheDocument(); | |||
let dialogScreen = within(dialog); | |||
await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' })); | |||
await user.click( | |||
dialogScreen.getByText(`${selectQPSuccess.name} - ${selectQPSuccess.languageName}`) | |||
); | |||
expect( | |||
dialogScreen.getByText(`${selectQPSuccess.name} - ${selectQPSuccess.languageName}`) | |||
).toBeInTheDocument(); | |||
await user.click(dialogScreen.getByRole('button', { name: 'apply' })); | |||
expect( | |||
dialogScreen.getByText( | |||
`coding_rules.bulk_change.success.${selectQPSuccess.name}.${selectQPSuccess.languageName}.${ | |||
handler.allRulesName().length | |||
}` | |||
) | |||
).toBeInTheDocument(); | |||
await user.click(dialogScreen.getByRole('button', { name: 'close' })); | |||
// Try bulk change when quality profile has warnning. | |||
handler.activateWithWarning(); | |||
await user.click(await screen.findByRole('button', { name: 'bulk_change' })); | |||
await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' })); | |||
dialogScreen = within( | |||
screen.getByRole('dialog', { | |||
name: 'coding_rules.activate_in_quality_profile (4 coding_rules._rules)' | |||
}) | |||
); | |||
await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.activate_in' })); | |||
await user.click( | |||
dialogScreen.getByText(`${selectQPWarning.name} - ${selectQPWarning.languageName}`) | |||
); | |||
await user.click(dialogScreen.getByRole('button', { name: 'apply' })); | |||
expect( | |||
dialogScreen.getByText( | |||
`coding_rules.bulk_change.warning.${selectQPWarning.name}.${ | |||
selectQPWarning.languageName | |||
}.${handler.allRulesName().length - 1}.1` | |||
) | |||
).toBeInTheDocument(); | |||
}); | |||
it('should be able to bulk deactivate quality profile', async () => { | |||
const user = userEvent.setup(); | |||
handler.setIsAdmin(); | |||
renderCodingRulesApp(mockLoggedInUser()); | |||
const selectQP = handler.allQualityProfile('java')[0]; | |||
await user.click(await screen.findByRole('button', { name: 'bulk_change' })); | |||
await user.click(await screen.findByRole('link', { name: 'coding_rules.deactivate_in…' })); | |||
const dialogScreen = within( | |||
screen.getByRole('dialog', { | |||
name: 'coding_rules.deactivate_in_quality_profile (4 coding_rules._rules)' | |||
}) | |||
); | |||
await user.click(dialogScreen.getByRole('textbox', { name: 'coding_rules.deactivate_in' })); | |||
await user.click(dialogScreen.getByText(`${selectQP.name} - ${selectQP.languageName}`)); | |||
await user.click(dialogScreen.getByRole('button', { name: 'apply' })); | |||
expect( | |||
dialogScreen.getByText( | |||
`coding_rules.bulk_change.success.${selectQP.name}.${selectQP.languageName}.${ | |||
handler.allRulesName().length | |||
}` | |||
) | |||
).toBeInTheDocument(); | |||
}); | |||
function renderCodingRulesApp(currentUser?: CurrentUser) { | |||
renderApp('coding_rules', routes, { | |||
currentUser, | |||
languages: { | |||
js: { key: 'js', name: 'JavaScript' }, | |||
java: { key: 'java', name: 'Java' }, | |||
c: { key: 'c', name: 'C' } | |||
} | |||
}); | |||
} |
@@ -19,10 +19,11 @@ | |||
*/ | |||
import classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { components, OptionProps, OptionTypeBase, SingleValueProps } from 'react-select'; | |||
import { activateRule, Profile } from '../../../api/quality-profiles'; | |||
import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import SelectLegacy from '../../../components/controls/SelectLegacy'; | |||
import Select from '../../../components/controls/Select'; | |||
import SeverityHelper from '../../../components/shared/SeverityHelper'; | |||
import { Alert } from '../../../components/ui/Alert'; | |||
import { SEVERITIES } from '../../../helpers/constants'; | |||
@@ -40,9 +41,13 @@ interface Props { | |||
rule: Rule | RuleDetails; | |||
} | |||
interface ProfileWithDeph extends Profile { | |||
depth: number; | |||
} | |||
interface State { | |||
params: Dict<string>; | |||
profile: string; | |||
profile?: ProfileWithDeph; | |||
severity: string; | |||
submitting: boolean; | |||
} | |||
@@ -55,7 +60,7 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat | |||
const profilesWithDepth = this.getQualityProfilesWithDepth(props); | |||
this.state = { | |||
params: this.getParams(props), | |||
profile: profilesWithDepth.length > 0 ? profilesWithDepth[0].key : '', | |||
profile: profilesWithDepth.length > 0 ? profilesWithDepth[0] : undefined, | |||
severity: props.activation ? props.activation.severity : props.rule.severity, | |||
submitting: false | |||
}; | |||
@@ -105,7 +110,7 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat | |||
event.preventDefault(); | |||
this.setState({ submitting: true }); | |||
const data = { | |||
key: this.state.profile, | |||
key: this.state.profile?.key || '', | |||
params: this.state.params, | |||
rule: this.props.rule.key, | |||
severity: this.state.severity | |||
@@ -132,18 +137,14 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat | |||
this.setState((state: State) => ({ params: { ...state.params, [name]: value } })); | |||
}; | |||
handleProfileChange = ({ value }: { value: string }) => { | |||
this.setState({ profile: value }); | |||
handleProfileChange = (profile: ProfileWithDeph) => { | |||
this.setState({ profile }); | |||
}; | |||
handleSeverityChange = ({ value }: { value: string }) => { | |||
handleSeverityChange = ({ value }: OptionTypeBase) => { | |||
this.setState({ severity: value }); | |||
}; | |||
renderSeverityOption = ({ value }: { value: string }) => { | |||
return <SeverityHelper severity={value} />; | |||
}; | |||
render() { | |||
const { activation, rule } = this.props; | |||
const { profile, severity, submitting } = this.state; | |||
@@ -152,6 +153,26 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat | |||
const isCustomRule = !!(rule as RuleDetails).templateKey; | |||
const activeInAllProfiles = profilesWithDepth.length <= 0; | |||
const isUpdateMode = !!activation; | |||
const serverityOption = SEVERITIES.map(severity => ({ | |||
label: translate('severity', severity), | |||
value: severity | |||
})); | |||
function Option(props: OptionProps<OptionTypeBase, false>) { | |||
return ( | |||
<components.Option {...props}> | |||
<SeverityHelper severity={props.data.value} /> | |||
</components.Option> | |||
); | |||
} | |||
function SingleValue(props: SingleValueProps<OptionTypeBase>) { | |||
return ( | |||
<components.SingleValue {...props}> | |||
<SeverityHelper className="coding-rules-severity-value" severity={props.data.value} /> | |||
</components.SingleValue> | |||
); | |||
} | |||
return ( | |||
<Modal contentLabel={this.props.modalHeader} onRequestClose={this.props.onClose} size="small"> | |||
@@ -166,34 +187,32 @@ export default class ActivationFormModal extends React.PureComponent<Props, Stat | |||
)} | |||
<div className="modal-field"> | |||
<label>{translate('coding_rules.quality_profile')}</label> | |||
<SelectLegacy | |||
<label id="coding-rules-quality-profile-select"> | |||
{translate('coding_rules.quality_profile')} | |||
</label> | |||
<Select | |||
className="js-profile" | |||
clearable={false} | |||
disabled={submitting || profilesWithDepth.length === 1} | |||
aria-labelledby="coding-rules-quality-profile-select" | |||
isClearable={false} | |||
isDisabled={submitting || profilesWithDepth.length === 1} | |||
onChange={this.handleProfileChange} | |||
options={profilesWithDepth.map(profile => ({ | |||
label: ' '.repeat(profile.depth) + profile.name, | |||
value: profile.key | |||
}))} | |||
getOptionLabel={p => ' '.repeat(p.depth) + p.name} | |||
options={profilesWithDepth} | |||
value={profile} | |||
/> | |||
</div> | |||
<div className="modal-field"> | |||
<label>{translate('severity')}</label> | |||
<SelectLegacy | |||
<label id="coding-rules-severity-select">{translate('severity')}</label> | |||
<Select | |||
className="js-severity" | |||
clearable={false} | |||
disabled={submitting} | |||
isClearable={false} | |||
isDisabled={submitting} | |||
aria-labelledby="coding-rules-severity-select" | |||
onChange={this.handleSeverityChange} | |||
optionRenderer={this.renderSeverityOption} | |||
options={SEVERITIES.map(severity => ({ | |||
label: translate('severity', severity), | |||
value: severity | |||
}))} | |||
searchable={false} | |||
value={severity} | |||
valueRenderer={this.renderSeverityOption} | |||
components={{ Option, SingleValue }} | |||
options={serverityOption} | |||
isSearchable={false} | |||
value={serverityOption.find(s => s.value === severity)} | |||
/> | |||
</div> | |||
{isCustomRule ? ( |
@@ -150,9 +150,7 @@ export class BulkChangeModal extends React.PureComponent<Props, State> { | |||
renderResult = (result: ActivationResult) => { | |||
const { profile: profileKey } = result; | |||
const profile = this.props.referencedProfiles[profileKey]; | |||
if (!profile) { | |||
return null; | |||
} | |||
const { languages } = this.props; | |||
const language = languages[profile.language] | |||
? languages[profile.language].name |
@@ -38,7 +38,6 @@ import RuleDetailsProfiles from './RuleDetailsProfiles'; | |||
interface Props { | |||
allowCustomRules?: boolean; | |||
canWrite?: boolean; | |||
hideQualityProfiles?: boolean; | |||
onActivate: (profile: string, rule: string, activation: Activation) => void; | |||
onDeactivate: (profile: string, rule: string) => void; | |||
onDelete: (rule: string) => void; | |||
@@ -156,7 +155,7 @@ export default class RuleDetails extends React.PureComponent<Props, State> { | |||
return <div className="coding-rule-details" />; | |||
} | |||
const { allowCustomRules, canWrite, hideQualityProfiles, referencedProfiles } = this.props; | |||
const { allowCustomRules, canWrite, referencedProfiles } = this.props; | |||
const { params = [] } = ruleDetails; | |||
const isCustom = !!ruleDetails.templateKey; | |||
@@ -236,7 +235,7 @@ export default class RuleDetails extends React.PureComponent<Props, State> { | |||
/> | |||
)} | |||
{!ruleDetails.isTemplate && !hideQualityProfiles && ( | |||
{!ruleDetails.isTemplate && ( | |||
<RuleDetailsProfiles | |||
activations={this.state.actives} | |||
canWrite={canWrite} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { activateRule } from '../../../../api/quality-profiles'; | |||
import { | |||
mockQualityProfile, | |||
mockRule, | |||
@@ -28,6 +29,10 @@ import { | |||
} from '../../../../helpers/testMocks'; | |||
import ActivationFormModal from '../ActivationFormModal'; | |||
jest.mock('../../../../api/quality-profiles', () => ({ | |||
activateRule: jest.fn().mockResolvedValueOnce({}) | |||
})); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect( | |||
@@ -47,6 +52,29 @@ it('should render correctly', () => { | |||
expect(wrapper).toMatchSnapshot('submitting'); | |||
}); | |||
it('should activate rule on quality profile when submit', () => { | |||
const wrapper = shallowRender(); | |||
wrapper.instance().handleFormSubmit(({ | |||
preventDefault: jest.fn() | |||
} as any) as React.SyntheticEvent<HTMLFormElement>); | |||
expect(activateRule).toHaveBeenCalledWith({ | |||
key: '', | |||
params: { | |||
'1': '1', | |||
'2': '1' | |||
}, | |||
rule: 'javascript:S1067', | |||
severity: 'MAJOR' | |||
}); | |||
}); | |||
it('should handle profile change correctly', () => { | |||
const wrapper = shallowRender(); | |||
const qualityProfile = mockQualityProfile(); | |||
wrapper.instance().handleProfileChange(qualityProfile); | |||
expect(wrapper.state().profile).toBe(qualityProfile); | |||
}); | |||
function shallowRender(props: Partial<ActivationFormModal['props']> = {}) { | |||
return shallow<ActivationFormModal>( | |||
<ActivationFormModal |
@@ -1,123 +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 { bulkActivateRules, bulkDeactivateRules } from '../../../../api/quality-profiles'; | |||
import { mockLanguage, mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import { submit, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { Query } from '../../query'; | |||
import { BulkChangeModal } from '../BulkChangeModal'; | |||
jest.mock('../../../../api/quality-profiles', () => ({ | |||
bulkActivateRules: jest.fn().mockResolvedValue({ failed: 0, succeeded: 2 }), | |||
bulkDeactivateRules: jest.fn().mockResolvedValue({ failed: 2, succeeded: 0 }) | |||
})); | |||
beforeEach(jest.clearAllMocks); | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ profile: undefined })).toMatchSnapshot('no profile pre-selected'); | |||
expect(shallowRender({ action: 'deactivate' })).toMatchSnapshot('deactivate action'); | |||
expect( | |||
shallowRender().setState({ | |||
results: [ | |||
{ failed: 2, profile: 'foo', succeeded: 0 }, | |||
{ failed: 0, profile: 'bar', succeeded: 2 } | |||
] | |||
}) | |||
).toMatchSnapshot('results'); | |||
expect(shallowRender().setState({ submitting: true })).toMatchSnapshot('submitting'); | |||
expect(shallowRender().setState({ finished: true })).toMatchSnapshot('finished'); | |||
}); | |||
it('should pre-select a profile if only 1 is available', () => { | |||
const profile = mockQualityProfile({ | |||
actions: { edit: true }, | |||
isBuiltIn: false, | |||
key: 'foo', | |||
language: 'js' | |||
}); | |||
const wrapper = shallowRender({ profile: undefined, referencedProfiles: { foo: profile } }); | |||
expect(wrapper.state().selectedProfiles).toEqual([profile]); | |||
}); | |||
it('should handle profile selection', () => { | |||
const wrapper = shallowRender(); | |||
const profiles = [mockQualityProfile({ name: 'foo' }), mockQualityProfile({ name: 'bar' })]; | |||
wrapper.instance().handleProfileSelect(profiles); | |||
expect(wrapper.state().selectedProfiles).toEqual(profiles); | |||
}); | |||
it('should handle form submission', async () => { | |||
const wrapper = shallowRender({ profile: undefined }); | |||
const profiles = [ | |||
mockQualityProfile({ name: 'foo', key: 'foo' }), | |||
mockQualityProfile({ name: 'bar', key: 'bar' }) | |||
]; | |||
wrapper.setState({ selectedProfiles: profiles }); | |||
// Activate. | |||
submit(wrapper.find('form')); | |||
await waitAndUpdate(wrapper); | |||
expect(bulkActivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'foo' })); | |||
await waitAndUpdate(wrapper); | |||
expect(bulkActivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'bar' })); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().results).toEqual([ | |||
{ failed: 0, profile: 'foo', succeeded: 2 }, | |||
{ failed: 0, profile: 'bar', succeeded: 2 } | |||
]); | |||
// Deactivate. | |||
wrapper.setProps({ action: 'deactivate' }).setState({ results: [] }); | |||
submit(wrapper.find('form')); | |||
await waitAndUpdate(wrapper); | |||
expect(bulkDeactivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'foo' })); | |||
await waitAndUpdate(wrapper); | |||
expect(bulkDeactivateRules).toBeCalledWith(expect.objectContaining({ targetKey: 'bar' })); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().results).toEqual([ | |||
{ failed: 2, profile: 'foo', succeeded: 0 }, | |||
{ failed: 2, profile: 'bar', succeeded: 0 } | |||
]); | |||
}); | |||
function shallowRender(props: Partial<BulkChangeModal['props']> = {}) { | |||
return shallow<BulkChangeModal>( | |||
<BulkChangeModal | |||
action="activate" | |||
languages={{ js: mockLanguage() }} | |||
onClose={jest.fn()} | |||
profile={mockQualityProfile()} | |||
query={{ languages: ['js'] } as Query} | |||
referencedProfiles={{ | |||
foo: mockQualityProfile({ key: 'foo' }), | |||
bar: mockQualityProfile({ key: 'bar' }) | |||
}} | |||
total={42} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -27,30 +27,42 @@ exports[`should render correctly: custom rule 1`] = ` | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-quality-profile-select" | |||
> | |||
coding_rules.quality_profile | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-quality-profile-select" | |||
className="js-profile" | |||
clearable={false} | |||
disabled={false} | |||
getOptionLabel={[Function]} | |||
isClearable={false} | |||
isDisabled={false} | |||
onChange={[Function]} | |||
options={Array []} | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-severity-select" | |||
> | |||
severity | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-severity-select" | |||
className="js-severity" | |||
clearable={false} | |||
disabled={false} | |||
components={ | |||
Object { | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
isClearable={false} | |||
isDisabled={false} | |||
isSearchable={false} | |||
onChange={[Function]} | |||
optionRenderer={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -75,9 +87,12 @@ exports[`should render correctly: custom rule 1`] = ` | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value="MAJOR" | |||
valueRenderer={[Function]} | |||
value={ | |||
Object { | |||
"label": "severity.MAJOR", | |||
"value": "MAJOR", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
@@ -136,30 +151,42 @@ exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-quality-profile-select" | |||
> | |||
coding_rules.quality_profile | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-quality-profile-select" | |||
className="js-profile" | |||
clearable={false} | |||
disabled={false} | |||
getOptionLabel={[Function]} | |||
isClearable={false} | |||
isDisabled={false} | |||
onChange={[Function]} | |||
options={Array []} | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-severity-select" | |||
> | |||
severity | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-severity-select" | |||
className="js-severity" | |||
clearable={false} | |||
disabled={false} | |||
components={ | |||
Object { | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
isClearable={false} | |||
isDisabled={false} | |||
isSearchable={false} | |||
onChange={[Function]} | |||
optionRenderer={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -184,9 +211,12 @@ exports[`should render correctly: default 1`] = ` | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value="MAJOR" | |||
valueRenderer={[Function]} | |||
value={ | |||
Object { | |||
"label": "severity.MAJOR", | |||
"value": "MAJOR", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
@@ -280,30 +310,42 @@ exports[`should render correctly: submitting 1`] = ` | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-quality-profile-select" | |||
> | |||
coding_rules.quality_profile | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-quality-profile-select" | |||
className="js-profile" | |||
clearable={false} | |||
disabled={true} | |||
getOptionLabel={[Function]} | |||
isClearable={false} | |||
isDisabled={true} | |||
onChange={[Function]} | |||
options={Array []} | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-severity-select" | |||
> | |||
severity | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-severity-select" | |||
className="js-severity" | |||
clearable={false} | |||
disabled={true} | |||
components={ | |||
Object { | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
isClearable={false} | |||
isDisabled={true} | |||
isSearchable={false} | |||
onChange={[Function]} | |||
optionRenderer={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -328,9 +370,12 @@ exports[`should render correctly: submitting 1`] = ` | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value="MAJOR" | |||
valueRenderer={[Function]} | |||
value={ | |||
Object { | |||
"label": "severity.MAJOR", | |||
"value": "MAJOR", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
@@ -422,30 +467,42 @@ exports[`should render correctly: update mode 1`] = ` | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-quality-profile-select" | |||
> | |||
coding_rules.quality_profile | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-quality-profile-select" | |||
className="js-profile" | |||
clearable={false} | |||
disabled={false} | |||
getOptionLabel={[Function]} | |||
isClearable={false} | |||
isDisabled={false} | |||
onChange={[Function]} | |||
options={Array []} | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-severity-select" | |||
> | |||
severity | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-severity-select" | |||
className="js-severity" | |||
clearable={false} | |||
disabled={false} | |||
components={ | |||
Object { | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
isClearable={false} | |||
isDisabled={false} | |||
isSearchable={false} | |||
onChange={[Function]} | |||
optionRenderer={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -470,9 +527,12 @@ exports[`should render correctly: update mode 1`] = ` | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value="MAJOR" | |||
valueRenderer={[Function]} | |||
value={ | |||
Object { | |||
"label": "severity.MAJOR", | |||
"value": "MAJOR", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
@@ -561,37 +621,81 @@ exports[`should render correctly: with deep profiles 1`] = ` | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-quality-profile-select" | |||
> | |||
coding_rules.quality_profile | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-quality-profile-select" | |||
className="js-profile" | |||
clearable={false} | |||
disabled={true} | |||
getOptionLabel={[Function]} | |||
isClearable={false} | |||
isDisabled={true} | |||
onChange={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "name", | |||
"value": "key", | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 0, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
}, | |||
] | |||
} | |||
value="key" | |||
value={ | |||
Object { | |||
"actions": Object { | |||
"edit": true, | |||
}, | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 0, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "key", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="modal-field" | |||
> | |||
<label> | |||
<label | |||
id="coding-rules-severity-select" | |||
> | |||
severity | |||
</label> | |||
<SelectLegacy | |||
<Select | |||
aria-labelledby="coding-rules-severity-select" | |||
className="js-severity" | |||
clearable={false} | |||
disabled={false} | |||
components={ | |||
Object { | |||
"Option": [Function], | |||
"SingleValue": [Function], | |||
} | |||
} | |||
isClearable={false} | |||
isDisabled={false} | |||
isSearchable={false} | |||
onChange={[Function]} | |||
optionRenderer={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
@@ -616,9 +720,12 @@ exports[`should render correctly: with deep profiles 1`] = ` | |||
}, | |||
] | |||
} | |||
searchable={false} | |||
value="MAJOR" | |||
valueRenderer={[Function]} | |||
value={ | |||
Object { | |||
"label": "severity.MAJOR", | |||
"value": "MAJOR", | |||
} | |||
} | |||
/> | |||
</div> | |||
<div |
@@ -1,313 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: deactivate action 1`] = ` | |||
<Modal | |||
contentLabel="coding_rules.deactivate_in_quality_profile (42 coding_rules._rules)" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
coding_rules.deactivate_in_quality_profile (42 coding_rules._rules) | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="modal-field" | |||
> | |||
<h3> | |||
<label | |||
id="coding-rules-bulk-change-profile" | |||
> | |||
coding_rules.deactivate_in | |||
</label> | |||
</h3> | |||
<span> | |||
name | |||
— | |||
are_you_sure | |||
</span> | |||
</div> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={false} | |||
id="coding-rules-submit-bulk-change" | |||
> | |||
apply | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<Modal | |||
contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
coding_rules.activate_in_quality_profile (42 coding_rules._rules) | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="modal-field" | |||
> | |||
<h3> | |||
<label | |||
id="coding-rules-bulk-change-profile" | |||
> | |||
coding_rules.activate_in | |||
</label> | |||
</h3> | |||
<span> | |||
name | |||
— | |||
are_you_sure | |||
</span> | |||
</div> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={false} | |||
id="coding-rules-submit-bulk-change" | |||
> | |||
apply | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: finished 1`] = ` | |||
<Modal | |||
contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
coding_rules.activate_in_quality_profile (42 coding_rules._rules) | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
/> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: no profile pre-selected 1`] = ` | |||
<Modal | |||
contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
coding_rules.activate_in_quality_profile (42 coding_rules._rules) | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="modal-field" | |||
> | |||
<h3> | |||
<label | |||
id="coding-rules-bulk-change-profile" | |||
> | |||
coding_rules.activate_in | |||
</label> | |||
</h3> | |||
<Select | |||
aria-labelledby="coding-rules-bulk-change-profile" | |||
getOptionLabel={[Function]} | |||
getOptionValue={[Function]} | |||
isClearable={false} | |||
isMulti={true} | |||
isSearchable={true} | |||
noOptionsMessage={[Function]} | |||
onChange={[Function]} | |||
options={Array []} | |||
value={Array []} | |||
/> | |||
</div> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={false} | |||
id="coding-rules-submit-bulk-change" | |||
> | |||
apply | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: results 1`] = ` | |||
<Modal | |||
contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
coding_rules.activate_in_quality_profile (42 coding_rules._rules) | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
> | |||
<Alert | |||
key="foo" | |||
variant="warning" | |||
> | |||
coding_rules.bulk_change.warning.name.CSS.0.2 | |||
</Alert> | |||
<Alert | |||
key="bar" | |||
variant="success" | |||
> | |||
coding_rules.bulk_change.success.name.CSS.2 | |||
</Alert> | |||
<div | |||
className="modal-field" | |||
> | |||
<h3> | |||
<label | |||
id="coding-rules-bulk-change-profile" | |||
> | |||
coding_rules.activate_in | |||
</label> | |||
</h3> | |||
<span> | |||
name | |||
— | |||
are_you_sure | |||
</span> | |||
</div> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={false} | |||
id="coding-rules-submit-bulk-change" | |||
> | |||
apply | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; | |||
exports[`should render correctly: submitting 1`] = ` | |||
<Modal | |||
contentLabel="coding_rules.activate_in_quality_profile (42 coding_rules._rules)" | |||
onRequestClose={[MockFunction]} | |||
size="small" | |||
> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
coding_rules.activate_in_quality_profile (42 coding_rules._rules) | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
/> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<i | |||
className="spinner spacer-right" | |||
/> | |||
<SubmitButton | |||
disabled={true} | |||
id="coding-rules-submit-bulk-change" | |||
> | |||
apply | |||
</SubmitButton> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
> | |||
cancel | |||
</ResetButtonLink> | |||
</footer> | |||
</form> | |||
</Modal> | |||
`; |
@@ -101,7 +101,8 @@ | |||
line-height: 1; | |||
} | |||
.coding-rules-details-tag-edit-cancel { | |||
.coding-rules-details-tag-edit-cancel, | |||
.coding-rules-severity-value svg { | |||
vertical-align: middle; | |||
} | |||
@@ -23,6 +23,7 @@ import { createStore, Store } from 'redux'; | |||
import { DocumentationEntry } from '../apps/documentation/utils'; | |||
import { Exporter, Profile } from '../apps/quality-profiles/types'; | |||
import { AppState } from '../types/appstate'; | |||
import { RuleRepository } from '../types/coding-rules'; | |||
import { EditionKey } from '../types/editions'; | |||
import { RawIssue } from '../types/issues'; | |||
import { Language } from '../types/languages'; | |||
@@ -832,8 +833,6 @@ export function mockDumpStatus(props: Partial<DumpStatus> = {}): DumpStatus { | |||
}; | |||
} | |||
export function mockRuleRepository( | |||
override: Partial<{ key: string; language: string; name: string }> = {} | |||
) { | |||
export function mockRuleRepository(override: Partial<RuleRepository> = {}) { | |||
return { key: 'css', language: 'css', name: 'SonarQube', ...override }; | |||
} |
@@ -27,12 +27,13 @@ import { createMemoryHistory, Route, RouteComponent, RouteConfig, Router } from | |||
import { Store } from 'redux'; | |||
import AppStateContextProvider from '../app/components/app-state/AppStateContextProvider'; | |||
import CurrentUserContextProvider from '../app/components/current-user/CurrentUserContextProvider'; | |||
import { LanguagesContext } from '../app/components/languages/LanguagesContext'; | |||
import { MetricsContext } from '../app/components/metrics/MetricsContext'; | |||
import getStore from '../app/utils/getStore'; | |||
import { RouteWithChildRoutes } from '../app/utils/startReactApp'; | |||
import { Store as State } from '../store/rootReducer'; | |||
import { AppState } from '../types/appstate'; | |||
import { Dict, Metric } from '../types/types'; | |||
import { Dict, Languages, Metric } from '../types/types'; | |||
import { CurrentUser } from '../types/users'; | |||
import { DEFAULT_METRICS } from './mocks/metrics'; | |||
import { mockAppState, mockCurrentUser } from './testMocks'; | |||
@@ -42,6 +43,7 @@ interface RenderContext { | |||
store?: Store<State, any>; | |||
history?: History; | |||
appState?: AppState; | |||
languages?: Languages; | |||
currentUser?: CurrentUser; | |||
} | |||
@@ -56,7 +58,7 @@ export function renderComponentApp( | |||
export function renderApp( | |||
indexPath: string, | |||
routes: RouteConfig, | |||
context: RenderContext | |||
context?: RenderContext | |||
): RenderResult { | |||
return renderRoutedApp( | |||
<RouteWithChildRoutes path={indexPath} childRoutes={routes} />, | |||
@@ -73,7 +75,8 @@ function renderRoutedApp( | |||
metrics = DEFAULT_METRICS, | |||
store = getStore(), | |||
appState = mockAppState(), | |||
history = createMemoryHistory() | |||
history = createMemoryHistory(), | |||
languages = {} | |||
}: RenderContext = {} | |||
): RenderResult { | |||
history.push(`/${indexPath}`); | |||
@@ -82,11 +85,13 @@ function renderRoutedApp( | |||
<IntlProvider defaultLocale="en" locale="en"> | |||
<MetricsContext.Provider value={metrics}> | |||
<Provider store={store}> | |||
<CurrentUserContextProvider currentUser={currentUser}> | |||
<AppStateContextProvider appState={appState}> | |||
<Router history={history}>{children}</Router> | |||
</AppStateContextProvider> | |||
</CurrentUserContextProvider> | |||
<LanguagesContext.Provider value={languages}> | |||
<CurrentUserContextProvider currentUser={currentUser}> | |||
<AppStateContextProvider appState={appState}> | |||
<Router history={history}>{children}</Router> | |||
</AppStateContextProvider> | |||
</CurrentUserContextProvider> | |||
</LanguagesContext.Provider> | |||
</Provider> | |||
</MetricsContext.Provider> | |||
</IntlProvider> |
@@ -19,9 +19,15 @@ | |||
*/ | |||
import { Dict, Rule, RuleActivation } from './types'; | |||
export interface RuleRepository { | |||
key: string; | |||
language: string; | |||
name: string; | |||
} | |||
export interface GetRulesAppResponse { | |||
canWrite?: boolean; | |||
repositories: { key: string; language: string; name: string }[]; | |||
repositories: RuleRepository[]; | |||
} | |||
export interface SearchRulesResponse { |
@@ -23,3 +23,32 @@ export enum RuleStatus { | |||
Deprecated = 'DEPRECATED', | |||
Removed = 'REMOVED' | |||
} | |||
export interface SearchRulesQuery { | |||
activation?: boolean | string; | |||
active_severities?: string; | |||
asc?: boolean | string; | |||
available_since?: string; | |||
cwe?: string; | |||
f?: string; | |||
facets?: string; | |||
include_external?: boolean | string; | |||
inheritance?: string; | |||
is_template?: boolean | string; | |||
languages?: string; | |||
owaspTop10?: string; | |||
p?: number; | |||
ps?: number; | |||
q?: string; | |||
qprofile?: string; | |||
repositories?: string; | |||
rule_key?: string; | |||
s?: string; | |||
sansTop25?: string; | |||
severities?: string; | |||
sonarsourceSecurity?: string; | |||
statuses?: string; | |||
tags?: string; | |||
template_key?: string; | |||
types?: string; | |||
} |