@@ -1,235 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { | |||
associateProject, | |||
dissociateProject, | |||
getProfileProjects, | |||
ProfileProject, | |||
searchQualityProfiles, | |||
} from '../../../api/quality-profiles'; | |||
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization'; | |||
import { mockComponent } from '../../../helpers/mocks/component'; | |||
import { waitAndUpdate } from '../../../helpers/testUtils'; | |||
import { ProjectQualityProfilesApp } from '../ProjectQualityProfilesApp'; | |||
jest.mock('../../../api/quality-profiles', () => { | |||
const { mockQualityProfile } = jest.requireActual('../../../helpers/testMocks'); | |||
return { | |||
associateProject: jest.fn().mockResolvedValue({}), | |||
dissociateProject: jest.fn().mockResolvedValue({}), | |||
searchQualityProfiles: jest.fn().mockResolvedValue({ | |||
profiles: [ | |||
mockQualityProfile({ key: 'css', language: 'css' }), | |||
mockQualityProfile({ key: 'css2', language: 'css' }), | |||
mockQualityProfile({ key: 'css_default', language: 'css', isDefault: true }), | |||
mockQualityProfile({ key: 'java', language: 'java' }), | |||
mockQualityProfile({ key: 'java_default', language: 'java', isDefault: true }), | |||
mockQualityProfile({ key: 'js', language: 'js' }), | |||
mockQualityProfile({ key: 'js_default', language: 'js', isDefault: true }), | |||
mockQualityProfile({ key: 'ts_default', language: 'ts', isDefault: true }), | |||
mockQualityProfile({ key: 'html', language: 'html' }), | |||
mockQualityProfile({ key: 'html_default', language: 'html', isDefault: true }), | |||
], | |||
}), | |||
getProfileProjects: jest.fn(({ key }) => { | |||
const results: ProfileProject[] = []; | |||
if (key === 'js' || key === 'css' || key === 'html_default') { | |||
results.push({ | |||
key: 'foo', | |||
name: 'Foo', | |||
selected: true, | |||
}); | |||
} else if (key === 'html') { | |||
results.push({ | |||
key: 'foobar', | |||
name: 'FooBar', | |||
selected: true, | |||
}); | |||
} | |||
return Promise.resolve({ results }); | |||
}), | |||
}; | |||
}); | |||
jest.mock('../../../helpers/globalMessages', () => ({ | |||
addGlobalSuccessMessage: jest.fn(), | |||
})); | |||
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn()); | |||
beforeEach(jest.clearAllMocks); | |||
it('renders correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('correctly checks permissions', () => { | |||
const wrapper = shallowRender({ | |||
component: mockComponent({ configuration: { showQualityProfiles: false } }), | |||
}); | |||
expect(wrapper.type()).toBeNull(); | |||
expect(handleRequiredAuthorization).toHaveBeenCalled(); | |||
}); | |||
it('correctly fetches and treats profile data', async () => { | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(searchQualityProfiles).toHaveBeenCalled(); | |||
expect(getProfileProjects).toHaveBeenCalledTimes(10); | |||
expect(wrapper.state().projectProfiles).toEqual([ | |||
expect.objectContaining({ | |||
profile: expect.objectContaining({ key: 'css' }), | |||
selected: true, | |||
}), | |||
expect.objectContaining({ | |||
profile: expect.objectContaining({ key: 'js' }), | |||
selected: true, | |||
}), | |||
expect.objectContaining({ | |||
profile: expect.objectContaining({ key: 'html_default' }), | |||
selected: true, | |||
}), | |||
expect.objectContaining({ | |||
profile: expect.objectContaining({ key: 'ts_default' }), | |||
selected: false, | |||
}), | |||
]); | |||
}); | |||
it('correctly sets a profile', async () => { | |||
const wrapper = shallowRender(); | |||
const instance = wrapper.instance(); | |||
await waitAndUpdate(wrapper); | |||
// Dissociate a selected profile. | |||
instance.handleSetProfile(undefined, 'css'); | |||
expect(dissociateProject).toHaveBeenLastCalledWith( | |||
expect.objectContaining({ key: 'css' }), | |||
'foo' | |||
); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().projectProfiles).toEqual( | |||
expect.arrayContaining([ | |||
{ | |||
profile: expect.objectContaining({ key: 'css_default' }), | |||
// It's not explicitly selected, as we're inheriting the default. | |||
selected: false, | |||
}, | |||
]) | |||
); | |||
// Associate a new profile. | |||
instance.handleSetProfile('css2', 'css_default'); | |||
expect(associateProject).toHaveBeenLastCalledWith( | |||
expect.objectContaining({ key: 'css2' }), | |||
'foo' | |||
); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().projectProfiles).toEqual( | |||
expect.arrayContaining([ | |||
{ | |||
profile: expect.objectContaining({ key: 'css2' }), | |||
// It's explicitly selected. | |||
selected: true, | |||
}, | |||
]) | |||
); | |||
// Dissociate a default profile that was inherited. | |||
(dissociateProject as jest.Mock).mockClear(); | |||
instance.handleSetProfile(undefined, 'ts_default'); | |||
// It won't call the WS. | |||
expect(dissociateProject).not.toHaveBeenCalled(); | |||
// Associate a default profile that was already inherited. | |||
instance.handleSetProfile('ts_default', 'ts_default'); | |||
expect(associateProject).toHaveBeenLastCalledWith( | |||
expect.objectContaining({ key: 'ts_default' }), | |||
'foo' | |||
); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().projectProfiles).toEqual( | |||
expect.arrayContaining([ | |||
{ | |||
profile: expect.objectContaining({ key: 'ts_default' }), | |||
// It's explicitly selected, even though it is the default profile. | |||
selected: true, | |||
}, | |||
]) | |||
); | |||
}); | |||
it('correctly adds a new language', async () => { | |||
const wrapper = shallowRender(); | |||
const instance = wrapper.instance(); | |||
await waitAndUpdate(wrapper); | |||
instance.handleAddLanguage('java'); | |||
expect(associateProject).toHaveBeenLastCalledWith( | |||
expect.objectContaining({ key: 'java' }), | |||
'foo' | |||
); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().projectProfiles).toEqual( | |||
expect.arrayContaining([ | |||
{ | |||
profile: expect.objectContaining({ key: 'java' }), | |||
// It must be explicitly selected. Adding an unanalyzed language can | |||
// only happen by explicitly choosing a profile. | |||
selected: true, | |||
}, | |||
]) | |||
); | |||
}); | |||
it('correctly handles WS errors', async () => { | |||
(searchQualityProfiles as jest.Mock).mockRejectedValueOnce(null); | |||
(getProfileProjects as jest.Mock).mockRejectedValueOnce(null); | |||
const wrapper = shallowRender(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().allProfiles).toHaveLength(0); | |||
expect(wrapper.state().projectProfiles).toHaveLength(0); | |||
expect(wrapper.state().loading).toBe(false); | |||
}); | |||
function shallowRender(props: Partial<ProjectQualityProfilesApp['props']> = {}) { | |||
return shallow<ProjectQualityProfilesApp>( | |||
<ProjectQualityProfilesApp | |||
component={mockComponent({ | |||
key: 'foo', | |||
configuration: { showQualityProfiles: true }, | |||
qualityProfiles: [ | |||
{ key: 'css2', name: 'CSS 2', language: 'css' }, | |||
{ key: 'js', name: 'JS', language: 'js' }, | |||
{ key: 'ts_default', name: 'TS (default)', language: 'ts' }, | |||
{ key: 'html', name: 'HTML', language: 'html' }, | |||
], | |||
})} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,91 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 { mockComponent } from '../../../helpers/mocks/component'; | |||
import { mockQualityProfile } from '../../../helpers/testMocks'; | |||
import ProjectQualityProfilesAppRenderer, { | |||
ProjectQualityProfilesAppRendererProps, | |||
} from '../ProjectQualityProfilesAppRenderer'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); | |||
expect( | |||
shallowRender({ | |||
showProjectProfileInModal: { | |||
profile: mockQualityProfile({ key: 'foo', language: 'js' }), | |||
selected: false, | |||
}, | |||
}) | |||
).toMatchSnapshot('open profile'); | |||
expect(shallowRender({ showAddLanguageModal: true })).toMatchSnapshot('add language'); | |||
}); | |||
function shallowRender(props: Partial<ProjectQualityProfilesAppRendererProps> = {}) { | |||
return shallow<ProjectQualityProfilesAppRendererProps>( | |||
<ProjectQualityProfilesAppRenderer | |||
allProfiles={[ | |||
mockQualityProfile({ key: 'foo', language: 'js' }), | |||
mockQualityProfile({ key: 'bar', language: 'css' }), | |||
mockQualityProfile({ key: 'baz', language: 'html' }), | |||
]} | |||
component={mockComponent()} | |||
loading={false} | |||
onAddLanguage={jest.fn()} | |||
onCloseModal={jest.fn()} | |||
onOpenAddLanguageModal={jest.fn()} | |||
onOpenSetProfileModal={jest.fn()} | |||
onSetProfile={jest.fn()} | |||
projectProfiles={[ | |||
{ | |||
profile: mockQualityProfile({ | |||
key: 'foo', | |||
name: 'Foo', | |||
isDefault: true, | |||
language: 'js', | |||
languageName: 'JS', | |||
}), | |||
selected: false, | |||
}, | |||
{ | |||
profile: mockQualityProfile({ | |||
key: 'bar', | |||
name: 'Bar', | |||
isDefault: true, | |||
language: 'css', | |||
languageName: 'CSS', | |||
}), | |||
selected: false, | |||
}, | |||
{ | |||
profile: mockQualityProfile({ | |||
key: 'baz', | |||
name: 'Baz', | |||
language: 'html', | |||
languageName: 'HTML', | |||
}), | |||
selected: true, | |||
}, | |||
]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,51 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`renders correctly 1`] = ` | |||
<ProjectQualityProfilesAppRenderer | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"configuration": { | |||
"showQualityProfiles": true, | |||
}, | |||
"key": "foo", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"key": "css2", | |||
"language": "css", | |||
"name": "CSS 2", | |||
}, | |||
{ | |||
"key": "js", | |||
"language": "js", | |||
"name": "JS", | |||
}, | |||
{ | |||
"key": "ts_default", | |||
"language": "ts", | |||
"name": "TS (default)", | |||
}, | |||
{ | |||
"key": "html", | |||
"language": "html", | |||
"name": "HTML", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
loading={true} | |||
onAddLanguage={[Function]} | |||
onCloseModal={[Function]} | |||
onOpenAddLanguageModal={[Function]} | |||
onOpenSetProfileModal={[Function]} | |||
onSetProfile={[Function]} | |||
/> | |||
`; |
@@ -1,920 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: add language 1`] = ` | |||
<div | |||
className="page page-limited" | |||
id="project-quality-profiles" | |||
> | |||
<Suggestions | |||
suggestions="project_quality_profiles" | |||
/> | |||
<Helmet | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
prioritizeSeoTags={false} | |||
title="project_quality_profiles.page" | |||
/> | |||
<A11ySkipTarget | |||
anchor="profiles_main" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="page-title display-flex-center" | |||
> | |||
<h1> | |||
project_quality_profiles.page | |||
</h1> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</div> | |||
</header> | |||
<div | |||
className="boxed-group" | |||
> | |||
<h2 | |||
className="boxed-group-header" | |||
> | |||
project_quality_profile.subtitle | |||
</h2> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
project_quality_profiles.page.description | |||
</p> | |||
<table | |||
className="data zebra" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
language | |||
</th> | |||
<th | |||
className="thin nowrap" | |||
> | |||
project_quality_profile.current | |||
</th> | |||
<th | |||
className="thin nowrap text-right" | |||
> | |||
coding_rules.filters.activation.active_rules | |||
</th> | |||
<th | |||
aria-label="actions" | |||
/> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr | |||
key="css" | |||
> | |||
<td> | |||
CSS | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
<em> | |||
project_quality_profile.instance_default | |||
</em> | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=bar", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
<tr | |||
key="html" | |||
> | |||
<td> | |||
HTML | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
Baz | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=baz", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
<tr | |||
key="js" | |||
> | |||
<td> | |||
JS | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
<em> | |||
project_quality_profile.instance_default | |||
</em> | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=foo", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<h2> | |||
project_quality_profile.add_language.title | |||
</h2> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
project_quality_profile.add_language.description | |||
</p> | |||
<Button | |||
disabled={false} | |||
onClick={[MockFunction]} | |||
> | |||
<PlusCircleIcon | |||
className="little-spacer-right" | |||
/> | |||
project_quality_profile.add_language.action | |||
</Button> | |||
</div> | |||
<withLanguagesContext(AddLanguageModal) | |||
onClose={[MockFunction]} | |||
onSubmit={[MockFunction]} | |||
profilesByLanguage={ | |||
{ | |||
"css": [ | |||
{ | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "bar", | |||
"language": "css", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
}, | |||
], | |||
"html": [ | |||
{ | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "baz", | |||
"language": "html", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
}, | |||
], | |||
"js": [ | |||
{ | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "foo", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
}, | |||
], | |||
} | |||
} | |||
unavailableLanguages={ | |||
[ | |||
"js", | |||
"css", | |||
"html", | |||
] | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: default 1`] = ` | |||
<div | |||
className="page page-limited" | |||
id="project-quality-profiles" | |||
> | |||
<Suggestions | |||
suggestions="project_quality_profiles" | |||
/> | |||
<Helmet | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
prioritizeSeoTags={false} | |||
title="project_quality_profiles.page" | |||
/> | |||
<A11ySkipTarget | |||
anchor="profiles_main" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="page-title display-flex-center" | |||
> | |||
<h1> | |||
project_quality_profiles.page | |||
</h1> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</div> | |||
</header> | |||
<div | |||
className="boxed-group" | |||
> | |||
<h2 | |||
className="boxed-group-header" | |||
> | |||
project_quality_profile.subtitle | |||
</h2> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
project_quality_profiles.page.description | |||
</p> | |||
<table | |||
className="data zebra" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
language | |||
</th> | |||
<th | |||
className="thin nowrap" | |||
> | |||
project_quality_profile.current | |||
</th> | |||
<th | |||
className="thin nowrap text-right" | |||
> | |||
coding_rules.filters.activation.active_rules | |||
</th> | |||
<th | |||
aria-label="actions" | |||
/> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr | |||
key="css" | |||
> | |||
<td> | |||
CSS | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
<em> | |||
project_quality_profile.instance_default | |||
</em> | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=bar", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
<tr | |||
key="html" | |||
> | |||
<td> | |||
HTML | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
Baz | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=baz", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
<tr | |||
key="js" | |||
> | |||
<td> | |||
JS | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
<em> | |||
project_quality_profile.instance_default | |||
</em> | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=foo", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<h2> | |||
project_quality_profile.add_language.title | |||
</h2> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
project_quality_profile.add_language.description | |||
</p> | |||
<Button | |||
disabled={false} | |||
onClick={[MockFunction]} | |||
> | |||
<PlusCircleIcon | |||
className="little-spacer-right" | |||
/> | |||
project_quality_profile.add_language.action | |||
</Button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: loading 1`] = ` | |||
<div | |||
className="page page-limited" | |||
id="project-quality-profiles" | |||
> | |||
<Suggestions | |||
suggestions="project_quality_profiles" | |||
/> | |||
<Helmet | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
prioritizeSeoTags={false} | |||
title="project_quality_profiles.page" | |||
/> | |||
<A11ySkipTarget | |||
anchor="profiles_main" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="page-title display-flex-center" | |||
> | |||
<h1> | |||
project_quality_profiles.page | |||
</h1> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</div> | |||
</header> | |||
<div | |||
className="boxed-group" | |||
> | |||
<h2 | |||
className="boxed-group-header" | |||
> | |||
project_quality_profile.subtitle | |||
</h2> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
project_quality_profiles.page.description | |||
</p> | |||
<i | |||
className="spinner spacer-left" | |||
/> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<h2> | |||
project_quality_profile.add_language.title | |||
</h2> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
project_quality_profile.add_language.description | |||
</p> | |||
<Button | |||
disabled={true} | |||
onClick={[MockFunction]} | |||
> | |||
<PlusCircleIcon | |||
className="little-spacer-right" | |||
/> | |||
project_quality_profile.add_language.action | |||
</Button> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly: open profile 1`] = ` | |||
<div | |||
className="page page-limited" | |||
id="project-quality-profiles" | |||
> | |||
<Suggestions | |||
suggestions="project_quality_profiles" | |||
/> | |||
<Helmet | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
prioritizeSeoTags={false} | |||
title="project_quality_profiles.page" | |||
/> | |||
<A11ySkipTarget | |||
anchor="profiles_main" | |||
/> | |||
<header | |||
className="page-header" | |||
> | |||
<div | |||
className="page-title display-flex-center" | |||
> | |||
<h1> | |||
project_quality_profiles.page | |||
</h1> | |||
<HelpTooltip | |||
className="spacer-left" | |||
overlay={ | |||
<div | |||
className="big-padded-top big-padded-bottom" | |||
> | |||
quality_profiles.list.projects.help | |||
</div> | |||
} | |||
/> | |||
</div> | |||
</header> | |||
<div | |||
className="boxed-group" | |||
> | |||
<h2 | |||
className="boxed-group-header" | |||
> | |||
project_quality_profile.subtitle | |||
</h2> | |||
<div | |||
className="boxed-group-inner" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
project_quality_profiles.page.description | |||
</p> | |||
<table | |||
className="data zebra" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
language | |||
</th> | |||
<th | |||
className="thin nowrap" | |||
> | |||
project_quality_profile.current | |||
</th> | |||
<th | |||
className="thin nowrap text-right" | |||
> | |||
coding_rules.filters.activation.active_rules | |||
</th> | |||
<th | |||
aria-label="actions" | |||
/> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr | |||
key="css" | |||
> | |||
<td> | |||
CSS | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
<em> | |||
project_quality_profile.instance_default | |||
</em> | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=bar", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
<tr | |||
key="html" | |||
> | |||
<td> | |||
HTML | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
Baz | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=baz", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
<tr | |||
key="js" | |||
> | |||
<td> | |||
JS | |||
</td> | |||
<td | |||
className="thin nowrap" | |||
> | |||
<span | |||
className="display-inline-flex-center" | |||
> | |||
<em> | |||
project_quality_profile.instance_default | |||
</em> | |||
</span> | |||
</td> | |||
<td | |||
className="nowrap text-right" | |||
> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/coding_rules", | |||
"search": "?activation=true&qprofile=foo", | |||
} | |||
} | |||
> | |||
10 | |||
</ForwardRef(Link)> | |||
</td> | |||
<td | |||
className="text-right" | |||
> | |||
<Button | |||
onClick={[Function]} | |||
> | |||
<EditIcon | |||
className="spacer-right" | |||
/> | |||
project_quality_profile.change_profile | |||
</Button> | |||
</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<h2> | |||
project_quality_profile.add_language.title | |||
</h2> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
> | |||
project_quality_profile.add_language.description | |||
</p> | |||
<Button | |||
disabled={false} | |||
onClick={[MockFunction]} | |||
> | |||
<PlusCircleIcon | |||
className="little-spacer-right" | |||
/> | |||
project_quality_profile.add_language.action | |||
</Button> | |||
</div> | |||
<SetQualityProfileModal | |||
availableProfiles={ | |||
[ | |||
{ | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "foo", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
}, | |||
] | |||
} | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
"key": "my-project", | |||
"name": "MyProject", | |||
"qualifier": "TRK", | |||
"qualityGate": { | |||
"isDefault": true, | |||
"key": "30", | |||
"name": "Sonar way", | |||
}, | |||
"qualityProfiles": [ | |||
{ | |||
"deleted": false, | |||
"key": "my-qp", | |||
"language": "ts", | |||
"name": "Sonar way", | |||
}, | |||
], | |||
"tags": [], | |||
} | |||
} | |||
currentProfile={ | |||
{ | |||
"activeDeprecatedRuleCount": 2, | |||
"activeRuleCount": 10, | |||
"childrenCount": 0, | |||
"depth": 1, | |||
"isBuiltIn": false, | |||
"isDefault": false, | |||
"isInherited": false, | |||
"key": "foo", | |||
"language": "js", | |||
"languageName": "JavaScript", | |||
"name": "name", | |||
"projectCount": 3, | |||
} | |||
} | |||
onClose={[MockFunction]} | |||
onSubmit={[MockFunction]} | |||
usesDefault={true} | |||
/> | |||
</div> | |||
</div> | |||
</div> | |||
`; |
@@ -0,0 +1,252 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 userEvent from '@testing-library/user-event'; | |||
import selectEvent from 'react-select-event'; | |||
import { | |||
ProfileProject, | |||
associateProject, | |||
getProfileProjects, | |||
searchQualityProfiles, | |||
} from '../../../api/quality-profiles'; | |||
import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization'; | |||
import { addGlobalSuccessMessage } from '../../../helpers/globalMessages'; | |||
import { mockComponent } from '../../../helpers/mocks/component'; | |||
import { | |||
RenderContext, | |||
renderAppWithComponentContext, | |||
} from '../../../helpers/testReactTestingUtils'; | |||
import { byLabelText, byRole, byText } from '../../../helpers/testSelector'; | |||
import { Component } from '../../../types/types'; | |||
import routes from '../routes'; | |||
jest.mock('../../../api/quality-profiles', () => { | |||
const { mockQualityProfile } = jest.requireActual('../../../helpers/testMocks'); | |||
return { | |||
associateProject: jest.fn().mockResolvedValue({}), | |||
dissociateProject: jest.fn().mockResolvedValue({}), | |||
searchQualityProfiles: jest.fn().mockResolvedValue({ | |||
profiles: [ | |||
mockQualityProfile({ | |||
key: 'css', | |||
language: 'css', | |||
name: 'css profile', | |||
languageName: 'CSS', | |||
}), | |||
mockQualityProfile({ | |||
key: 'java', | |||
language: 'java', | |||
name: 'java profile', | |||
languageName: 'Java', | |||
}), | |||
mockQualityProfile({ | |||
key: 'js', | |||
language: 'js', | |||
name: 'js profile', | |||
languageName: 'JavaScript', | |||
}), | |||
mockQualityProfile({ | |||
key: 'ts', | |||
language: 'ts', | |||
isDefault: true, | |||
name: 'ts profile', | |||
languageName: 'Typescript', | |||
}), | |||
mockQualityProfile({ | |||
key: 'html', | |||
language: 'html', | |||
name: 'html profile', | |||
languageName: 'HTML', | |||
}), | |||
mockQualityProfile({ | |||
key: 'html_default', | |||
language: 'html', | |||
isDefault: true, | |||
isBuiltIn: true, | |||
name: 'html default profile', | |||
languageName: 'HTML', | |||
}), | |||
], | |||
}), | |||
getProfileProjects: jest.fn(({ key }) => { | |||
const results: ProfileProject[] = []; | |||
if (key === 'css' || key === 'java' || key === 'js' || key === 'ts' || key === 'java') { | |||
results.push({ | |||
key: 'my-project', | |||
name: 'My project', | |||
selected: true, | |||
}); | |||
} | |||
return Promise.resolve({ results }); | |||
}), | |||
}; | |||
}); | |||
jest.mock('../../../helpers/globalMessages', () => { | |||
const globalMessages = jest.requireActual('../../../helpers/globalMessages'); | |||
return { | |||
...globalMessages, | |||
addGlobalSuccessMessage: jest.fn(), | |||
}; | |||
}); | |||
jest.mock('../../../app/utils/handleRequiredAuthorization', () => jest.fn()); | |||
beforeEach(jest.clearAllMocks); | |||
const ui = { | |||
pageTitle: byText('project_quality_profiles.page'), | |||
pageSubTitle: byText('project_quality_profile.subtitle'), | |||
pageDescription: byText('project_quality_profiles.page.description'), | |||
helpTooltip: byLabelText('help'), | |||
profileRows: byRole('row'), | |||
addLanguageButton: byRole('button', { name: 'project_quality_profile.add_language.action' }), | |||
modalAddLanguageTitle: byText('project_quality_profile.add_language_modal.title'), | |||
selectLanguage: byRole('combobox', { | |||
name: 'project_quality_profile.add_language_modal.choose_language', | |||
}), | |||
selectProfile: byRole('combobox', { | |||
name: 'project_quality_profile.add_language_modal.choose_profile', | |||
}), | |||
selectUseSpecificProfile: byRole('combobox', { | |||
name: 'project_quality_profile.always_use_specific', | |||
}), | |||
buttonSave: byRole('button', { name: 'save' }), | |||
buttonChangeProfile: byRole('button', { name: 'project_quality_profile.change_profile' }), | |||
htmlLanguage: byText('HTML'), | |||
htmlProfile: byText('html profile'), | |||
cssLanguage: byText('CSS'), | |||
cssProfile: byText('css profile'), | |||
htmlDefaultProfile: byText('html default profile'), | |||
htmlActiveRuleslink: byRole('link', { name: '10' }), | |||
radioButtonUseInstanceDefault: byRole('radio', { | |||
name: /project_quality_profile.always_use_default/, | |||
}), | |||
radioButtonUseSpecific: byRole('radio', { name: /project_quality_profile.always_use_specific/ }), | |||
newAnalysisWarningMessage: byText('project_quality_profile.requires_new_analysis'), | |||
builtInTag: byText('quality_profiles.built_in'), | |||
}; | |||
it('should be able to add and change profile for languages', async () => { | |||
const user = userEvent.setup(); | |||
renderProjectQualityProfilesApp({ | |||
languages: { | |||
css: { key: 'css', name: 'CSS' }, | |||
ts: { key: 'ts', name: 'TS' }, | |||
js: { key: 'js', name: 'JS' }, | |||
java: { key: 'java', name: 'JAVA' }, | |||
html: { key: 'html', name: 'HTML' }, | |||
}, | |||
}); | |||
expect(ui.pageTitle.get()).toBeInTheDocument(); | |||
expect(ui.pageSubTitle.get()).toBeInTheDocument(); | |||
expect(ui.pageDescription.get()).toBeInTheDocument(); | |||
expect(ui.addLanguageButton.get()).toBeInTheDocument(); | |||
await expect(ui.helpTooltip.get()).toHaveATooltipWithContent( | |||
'quality_profiles.list.projects.help' | |||
); | |||
expect(ui.profileRows.getAll()).toHaveLength(5); | |||
expect(ui.cssLanguage.get()).toBeInTheDocument(); | |||
expect(ui.cssProfile.get()).toBeInTheDocument(); | |||
await user.click(ui.addLanguageButton.get()); | |||
// Opens the add language modal | |||
expect(ui.modalAddLanguageTitle.get()).toBeInTheDocument(); | |||
expect(ui.selectLanguage.get()).toBeEnabled(); | |||
expect(ui.selectProfile.get()).toBeDisabled(); | |||
expect(ui.buttonSave.get()).toBeInTheDocument(); | |||
await selectEvent.select(ui.selectLanguage.get(), 'HTML'); | |||
expect(ui.selectProfile.get()).toBeEnabled(); | |||
await selectEvent.select(ui.selectProfile.get(), 'html profile'); | |||
await user.click(ui.buttonSave.get()); | |||
expect(associateProject).toHaveBeenLastCalledWith( | |||
expect.objectContaining({ key: 'html', name: 'html profile' }), | |||
'my-project' | |||
); | |||
expect(addGlobalSuccessMessage).toHaveBeenCalledWith( | |||
'project_quality_profile.successfully_updated.HTML' | |||
); | |||
// Updates the page after API call | |||
const htmlRow = byRole('row', { | |||
name: 'HTML html profile 10 project_quality_profile.change_profile', | |||
}); | |||
expect(ui.htmlLanguage.get()).toBeInTheDocument(); | |||
expect(ui.htmlProfile.get()).toBeInTheDocument(); | |||
expect(ui.profileRows.getAll()).toHaveLength(6); | |||
expect(htmlRow.get()).toBeInTheDocument(); | |||
expect(htmlRow.byRole('link', { name: '10' }).get()).toHaveAttribute( | |||
'href', | |||
'/coding_rules?activation=true&qprofile=html' | |||
); | |||
expect(ui.builtInTag.query()).not.toBeInTheDocument(); | |||
await user.click( | |||
htmlRow.byRole('button', { name: 'project_quality_profile.change_profile' }).get() | |||
); | |||
//Opens modal to change profile | |||
expect(ui.radioButtonUseInstanceDefault.get()).not.toBeChecked(); | |||
expect(ui.radioButtonUseSpecific.get()).toBeChecked(); | |||
expect(ui.newAnalysisWarningMessage.get()).toBeInTheDocument(); | |||
expect(ui.selectUseSpecificProfile.get()).toBeInTheDocument(); | |||
await selectEvent.select(ui.selectUseSpecificProfile.get(), 'html default profile'); | |||
await user.click(ui.buttonSave.get()); | |||
expect(addGlobalSuccessMessage).toHaveBeenCalledWith( | |||
'project_quality_profile.successfully_updated.HTML' | |||
); | |||
// Updates the page after API call | |||
expect(ui.htmlProfile.query()).not.toBeInTheDocument(); | |||
expect(ui.htmlDefaultProfile.get()).toBeInTheDocument(); | |||
expect(ui.builtInTag.get()).toBeInTheDocument(); | |||
}); | |||
it('should call authorization api when permissions is not proper', () => { | |||
renderProjectQualityProfilesApp({}, { configuration: { showQualityProfiles: false } }); | |||
expect(handleRequiredAuthorization).toHaveBeenCalled(); | |||
}); | |||
it('should still show page with add language button when api fails', () => { | |||
jest.mocked(searchQualityProfiles).mockRejectedValueOnce(null); | |||
jest.mocked(getProfileProjects).mockRejectedValueOnce(null); | |||
renderProjectQualityProfilesApp(); | |||
expect(ui.pageTitle.get()).toBeInTheDocument(); | |||
expect(ui.pageSubTitle.get()).toBeInTheDocument(); | |||
expect(ui.pageDescription.get()).toBeInTheDocument(); | |||
expect(ui.addLanguageButton.get()).toBeInTheDocument(); | |||
}); | |||
function renderProjectQualityProfilesApp( | |||
context?: RenderContext, | |||
componentOverrides: Partial<Component> = { configuration: { showQualityProfiles: true } } | |||
) { | |||
return renderAppWithComponentContext('project/quality_profiles', routes, context, { | |||
component: mockComponent(componentOverrides), | |||
}); | |||
} |
@@ -21,9 +21,9 @@ import { difference } from 'lodash'; | |||
import * as React from 'react'; | |||
import { Profile } from '../../../api/quality-profiles'; | |||
import withLanguagesContext from '../../../app/components/languages/withLanguagesContext'; | |||
import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; | |||
import Select, { LabelValueSelectOption } from '../../../components/controls/Select'; | |||
import SimpleModal from '../../../components/controls/SimpleModal'; | |||
import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Languages } from '../../../types/languages'; | |||
import { Dict } from '../../../types/types'; | |||
@@ -90,6 +90,9 @@ export function AddLanguageModal(props: AddLanguageModalProps) { | |||
className="abs-width-300" | |||
isDisabled={submitting} | |||
id="language" | |||
aria-label={translate( | |||
'project_quality_profile.add_language_modal.choose_language' | |||
)} | |||
onChange={({ value }: LabelValueSelectOption) => { | |||
setSelected({ language: value, key: undefined }); | |||
}} | |||
@@ -107,6 +110,9 @@ export function AddLanguageModal(props: AddLanguageModalProps) { | |||
className="abs-width-300" | |||
isDisabled={submitting || !language} | |||
id="profiles" | |||
aria-label={translate( | |||
'project_quality_profile.add_language_modal.choose_profile' | |||
)} | |||
onChange={({ value }: ProfileOption) => setSelected({ language, key: value })} | |||
options={profileOptions} | |||
components={{ |
@@ -19,10 +19,10 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { Profile } from '../../../api/quality-profiles'; | |||
import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; | |||
import Radio from '../../../components/controls/Radio'; | |||
import Select from '../../../components/controls/Select'; | |||
import SimpleModal from '../../../components/controls/SimpleModal'; | |||
import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; | |||
import { Alert } from '../../../components/ui/Alert'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { Component } from '../../../types/types'; | |||
@@ -126,6 +126,7 @@ export default function SetQualityProfileModal(props: SetQualityProfileModalProp | |||
<div className="display-flex-center"> | |||
<Select | |||
className="abs-width-300" | |||
aria-label={translate('project_quality_profile.always_use_specific')} | |||
isDisabled={submitting || hasSelectedSysDefault} | |||
onChange={({ value }: ProfileOption) => setSelected(value)} | |||
options={profileOptions} |
@@ -1,106 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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, ShallowWrapper } from 'enzyme'; | |||
import * as React from 'react'; | |||
import Select from '../../../../components/controls/Select'; | |||
import SimpleModal from '../../../../components/controls/SimpleModal'; | |||
import { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import { AddLanguageModal, AddLanguageModalProps } from '../AddLanguageModal'; | |||
it('should render correctly', () => { | |||
expect(diveIntoSimpleModal(shallowRender())).toMatchSnapshot('default'); | |||
}); | |||
it('should correctly handle changes', () => { | |||
const onSubmit = jest.fn(); | |||
const wrapper = shallowRender({ onSubmit }); | |||
const langSelect = getLanguageSelect(wrapper); | |||
let profileSelect = getProfileSelect(wrapper); | |||
// Language select should only have 2; JS is not available. Profile Select | |||
// should have none, as no language is selected yet. | |||
expect(langSelect.props().options).toHaveLength(2); | |||
expect(profileSelect.props().options).toHaveLength(0); | |||
// Choose CSS. | |||
const langChange = langSelect.props().onChange; | |||
expect(langChange).toBeDefined(); | |||
langChange!({ value: 'css' }); | |||
// Should now show 2 available profiles. | |||
profileSelect = getProfileSelect(wrapper); | |||
expect(profileSelect.props().options).toHaveLength(2); | |||
expect(profileSelect.props().options).toEqual( | |||
expect.arrayContaining([expect.objectContaining({ isDisabled: true })]) | |||
); | |||
// Choose 1 profile. | |||
const profileChange = profileSelect.props().onChange; | |||
expect(profileChange).toBeDefined(); | |||
profileChange!({ value: 'css2' }); | |||
submitSimpleModal(wrapper); | |||
expect(onSubmit).toHaveBeenLastCalledWith('css2'); | |||
}); | |||
function diveIntoSimpleModal(wrapper: ShallowWrapper) { | |||
return wrapper.find(SimpleModal).dive().children(); | |||
} | |||
function getLanguageSelect(wrapper: ShallowWrapper) { | |||
return diveIntoSimpleModal(wrapper).find(Select).at(0); | |||
} | |||
function getProfileSelect(wrapper: ShallowWrapper) { | |||
return diveIntoSimpleModal(wrapper).find(Select).at(1); | |||
} | |||
function submitSimpleModal(wrapper: ShallowWrapper) { | |||
wrapper.find(SimpleModal).props().onSubmit(); | |||
} | |||
function shallowRender(props: Partial<AddLanguageModalProps> = {}) { | |||
return shallow<AddLanguageModalProps>( | |||
<AddLanguageModal | |||
languages={{ | |||
css: { key: 'css', name: 'CSS' }, | |||
ts: { key: 'ts', name: 'TS' }, | |||
js: { key: 'js', name: 'JS' }, | |||
}} | |||
onClose={jest.fn()} | |||
onSubmit={jest.fn()} | |||
profilesByLanguage={{ | |||
css: [ | |||
mockQualityProfile({ key: 'css', name: 'CSS', activeRuleCount: 0 }), | |||
mockQualityProfile({ key: 'css2', name: 'CSS 2' }), | |||
], | |||
ts: [mockQualityProfile({ key: 'ts', name: 'TS' })], | |||
js: [mockQualityProfile({ key: 'js', name: 'JS' })], | |||
}} | |||
unavailableLanguages={['js']} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,52 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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 DisableableSelectOption from '../../../../components/common/DisableableSelectOption'; | |||
import { mockProfileOption } from '../../../../helpers/mocks/quality-profiles'; | |||
import LanguageProfileSelectOption, { | |||
LanguageProfileSelectOptionProps, | |||
ProfileOption, | |||
} from '../LanguageProfileSelectOption'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
}); | |||
describe('tooltip', () => { | |||
it('should render correctly', () => { | |||
expect(renderTooltipOverly()).toMatchSnapshot('default'); | |||
expect(renderTooltipOverly({ label: undefined })).toMatchSnapshot('no link'); | |||
}); | |||
}); | |||
function renderTooltipOverly(overrides: Partial<ProfileOption> = {}) { | |||
const wrapper = shallowRender(overrides); | |||
const { disableTooltipOverlay } = wrapper.find(DisableableSelectOption).props(); | |||
return disableTooltipOverlay(); | |||
} | |||
function shallowRender(overrides: Partial<ProfileOption> = {}) { | |||
// satisfy the required props that the option actually gets from the select | |||
const optionProps = {} as LanguageProfileSelectOptionProps; | |||
return shallow( | |||
<LanguageProfileSelectOption {...optionProps} data={{ ...mockProfileOption(), ...overrides }} /> | |||
); | |||
} |
@@ -1,82 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 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, ShallowWrapper } from 'enzyme'; | |||
import * as React from 'react'; | |||
import Radio from '../../../../components/controls/Radio'; | |||
import Select from '../../../../components/controls/Select'; | |||
import SimpleModal from '../../../../components/controls/SimpleModal'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockQualityProfile } from '../../../../helpers/testMocks'; | |||
import SetQualityProfileModal, { SetQualityProfileModalProps } from '../SetQualityProfileModal'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('default'); | |||
expect(shallowRender({ usesDefault: true })).toMatchSnapshot('inherits system default'); | |||
expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('needs reanalysis'); | |||
}); | |||
it('should correctly handle changes', () => { | |||
const onSubmit = jest.fn(); | |||
const wrapper = shallowRender({ onSubmit }, false); | |||
diveIntoSimpleModal(wrapper).find(Radio).at(0).props().onCheck(''); | |||
submitSimpleModal(wrapper); | |||
expect(onSubmit).toHaveBeenLastCalledWith(undefined, 'foo'); | |||
diveIntoSimpleModal(wrapper).find(Radio).at(1).props().onCheck(''); | |||
diveIntoSimpleModal(wrapper).find(Select).props().onChange({ value: 'bar' }); | |||
submitSimpleModal(wrapper); | |||
expect(onSubmit).toHaveBeenLastCalledWith('bar', 'foo'); | |||
const change = diveIntoSimpleModal(wrapper).find(Select).props().onChange; | |||
expect(change).toBeDefined(); | |||
change!({ value: 'bar' }); | |||
submitSimpleModal(wrapper); | |||
expect(onSubmit).toHaveBeenLastCalledWith('bar', 'foo'); | |||
}); | |||
function diveIntoSimpleModal(wrapper: ShallowWrapper) { | |||
return wrapper.find(SimpleModal).dive().children(); | |||
} | |||
function submitSimpleModal(wrapper: ShallowWrapper) { | |||
wrapper.find(SimpleModal).props().onSubmit(); | |||
} | |||
function shallowRender(props: Partial<SetQualityProfileModalProps> = {}, dive = true) { | |||
const wrapper = shallow<SetQualityProfileModalProps>( | |||
<SetQualityProfileModal | |||
availableProfiles={[ | |||
mockQualityProfile({ key: 'foo', isDefault: true, language: 'js' }), | |||
mockQualityProfile({ key: 'bar', language: 'js', activeRuleCount: 0 }), | |||
]} | |||
component={mockComponent({ qualityProfiles: [{ key: 'foo', name: 'Foo', language: 'js' }] })} | |||
currentProfile={mockQualityProfile({ key: 'foo', language: 'js' })} | |||
onClose={jest.fn()} | |||
onSubmit={jest.fn()} | |||
usesDefault={false} | |||
{...props} | |||
/> | |||
); | |||
return dive ? diveIntoSimpleModal(wrapper) : wrapper; | |||
} |
@@ -1,95 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
[ | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
project_quality_profile.add_language_modal.title | |||
</h2> | |||
</div>, | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<label | |||
className="text-bold" | |||
htmlFor="language" | |||
> | |||
project_quality_profile.add_language_modal.choose_language | |||
</label> | |||
</div> | |||
<Select | |||
className="abs-width-300" | |||
id="language" | |||
isDisabled={false} | |||
onChange={[Function]} | |||
options={ | |||
[ | |||
{ | |||
"label": "CSS", | |||
"value": "css", | |||
}, | |||
{ | |||
"label": "TS", | |||
"value": "ts", | |||
}, | |||
] | |||
} | |||
/> | |||
</div> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
<label | |||
className="text-bold" | |||
htmlFor="profiles" | |||
> | |||
project_quality_profile.add_language_modal.choose_profile | |||
</label> | |||
</div> | |||
<Select | |||
className="abs-width-300" | |||
components={ | |||
{ | |||
"Option": [Function], | |||
} | |||
} | |||
id="profiles" | |||
isDisabled={true} | |||
onChange={[Function]} | |||
options={[]} | |||
value={null} | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<ButtonLink | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
cancel | |||
</ButtonLink> | |||
</div> | |||
</form>, | |||
] | |||
`; |
@@ -1,55 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
<Option | |||
data={ | |||
{ | |||
"isDisabled": false, | |||
"label": "Profile 1", | |||
"language": "Java", | |||
"value": "profile-1", | |||
} | |||
} | |||
> | |||
<div> | |||
<DisableableSelectOption | |||
disableTooltipOverlay={[Function]} | |||
disabledReason="project_quality_profile.add_language_modal.no_active_rules" | |||
option={ | |||
{ | |||
"isDisabled": false, | |||
"label": "Profile 1", | |||
"language": "Java", | |||
"value": "profile-1", | |||
} | |||
} | |||
/> | |||
</div> | |||
</Option> | |||
`; | |||
exports[`tooltip should render correctly: default 1`] = ` | |||
<React.Fragment> | |||
<p> | |||
project_quality_profile.add_language_modal.profile_unavailable_no_active_rules | |||
</p> | |||
<ForwardRef(Link) | |||
to={ | |||
{ | |||
"pathname": "/profiles/show", | |||
"search": "?name=Profile+1&language=Java", | |||
} | |||
} | |||
> | |||
project_quality_profile.add_language_modal.go_to_profile | |||
</ForwardRef(Link)> | |||
</React.Fragment> | |||
`; | |||
exports[`tooltip should render correctly: no link 1`] = ` | |||
<React.Fragment> | |||
<p> | |||
project_quality_profile.add_language_modal.profile_unavailable_no_active_rules | |||
</p> | |||
</React.Fragment> | |||
`; |
@@ -1,384 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: default 1`] = ` | |||
[ | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
project_quality_profile.change_lang_X_profile.JavaScript | |||
</h2> | |||
</div>, | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<Radio | |||
checked={false} | |||
className="display-flex-start" | |||
disabled={false} | |||
onCheck={[Function]} | |||
value="-1" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
project_quality_profile.always_use_default | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<span | |||
className="text-muted spacer-right" | |||
> | |||
current_noun | |||
: | |||
</span> | |||
name | |||
</div> | |||
</div> | |||
</Radio> | |||
</div> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<Radio | |||
checked={true} | |||
className="display-flex-start" | |||
disabled={false} | |||
onCheck={[Function]} | |||
value="foo" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
project_quality_profile.always_use_specific | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<Select | |||
className="abs-width-300" | |||
components={ | |||
{ | |||
"Option": [Function], | |||
} | |||
} | |||
isDisabled={false} | |||
onChange={[Function]} | |||
options={ | |||
[ | |||
{ | |||
"isDisabled": false, | |||
"label": "name", | |||
"language": "js", | |||
"value": "foo", | |||
}, | |||
{ | |||
"isDisabled": true, | |||
"label": "name", | |||
"language": "js", | |||
"value": "bar", | |||
}, | |||
] | |||
} | |||
value={ | |||
{ | |||
"isDisabled": false, | |||
"label": "name", | |||
"language": "js", | |||
"value": "foo", | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</Radio> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<ButtonLink | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
cancel | |||
</ButtonLink> | |||
</div> | |||
</form>, | |||
] | |||
`; | |||
exports[`should render correctly: inherits system default 1`] = ` | |||
[ | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
project_quality_profile.change_lang_X_profile.JavaScript | |||
</h2> | |||
</div>, | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<Radio | |||
checked={true} | |||
className="display-flex-start" | |||
disabled={false} | |||
onCheck={[Function]} | |||
value="-1" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
project_quality_profile.always_use_default | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<span | |||
className="text-muted spacer-right" | |||
> | |||
current_noun | |||
: | |||
</span> | |||
name | |||
</div> | |||
</div> | |||
</Radio> | |||
</div> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<Radio | |||
checked={false} | |||
className="display-flex-start" | |||
disabled={false} | |||
onCheck={[Function]} | |||
value="foo" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
project_quality_profile.always_use_specific | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<Select | |||
className="abs-width-300" | |||
components={ | |||
{ | |||
"Option": [Function], | |||
} | |||
} | |||
isDisabled={true} | |||
onChange={[Function]} | |||
options={ | |||
[ | |||
{ | |||
"isDisabled": false, | |||
"label": "name", | |||
"language": "js", | |||
"value": "foo", | |||
}, | |||
{ | |||
"isDisabled": true, | |||
"label": "name", | |||
"language": "js", | |||
"value": "bar", | |||
}, | |||
] | |||
} | |||
value={ | |||
{ | |||
"isDisabled": false, | |||
"label": "name", | |||
"language": "js", | |||
"value": "foo", | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</Radio> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<ButtonLink | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
cancel | |||
</ButtonLink> | |||
</div> | |||
</form>, | |||
] | |||
`; | |||
exports[`should render correctly: needs reanalysis 1`] = ` | |||
[ | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
project_quality_profile.change_lang_X_profile.JavaScript | |||
</h2> | |||
</div>, | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="modal-body" | |||
> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<Radio | |||
checked={false} | |||
className="display-flex-start" | |||
disabled={false} | |||
onCheck={[Function]} | |||
value="-1" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
project_quality_profile.always_use_default | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<span | |||
className="text-muted spacer-right" | |||
> | |||
current_noun | |||
: | |||
</span> | |||
name | |||
</div> | |||
</div> | |||
</Radio> | |||
</div> | |||
<div | |||
className="big-spacer-bottom" | |||
> | |||
<Radio | |||
checked={true} | |||
className="display-flex-start" | |||
disabled={false} | |||
onCheck={[Function]} | |||
value="foo" | |||
> | |||
<div | |||
className="spacer-left" | |||
> | |||
<div | |||
className="little-spacer-bottom" | |||
> | |||
project_quality_profile.always_use_specific | |||
</div> | |||
<div | |||
className="display-flex-center" | |||
> | |||
<Select | |||
className="abs-width-300" | |||
components={ | |||
{ | |||
"Option": [Function], | |||
} | |||
} | |||
isDisabled={false} | |||
onChange={[Function]} | |||
options={ | |||
[ | |||
{ | |||
"isDisabled": false, | |||
"label": "name", | |||
"language": "js", | |||
"value": "foo", | |||
}, | |||
{ | |||
"isDisabled": true, | |||
"label": "name", | |||
"language": "js", | |||
"value": "bar", | |||
}, | |||
] | |||
} | |||
value={ | |||
{ | |||
"isDisabled": false, | |||
"label": "name", | |||
"language": "js", | |||
"value": "foo", | |||
} | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</Radio> | |||
</div> | |||
<Alert | |||
variant="warning" | |||
> | |||
project_quality_profile.requires_new_analysis | |||
</Alert> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
save | |||
</SubmitButton> | |||
<ButtonLink | |||
disabled={false} | |||
onClick={[Function]} | |||
> | |||
cancel | |||
</ButtonLink> | |||
</div> | |||
</form>, | |||
] | |||
`; |