@@ -98,6 +98,63 @@ export default class IssuesServiceMock { | |||
constructor() { | |||
this.defaultList = [ | |||
{ | |||
issue: mockRawIssue(false, { | |||
key: 'issue101', | |||
component: 'foo:test1.js', | |||
message: 'Issue with no location message', | |||
rule: 'simpleRuleId', | |||
textRange: { | |||
startLine: 10, | |||
endLine: 10, | |||
startOffset: 0, | |||
endOffset: 2, | |||
}, | |||
flows: [ | |||
{ | |||
locations: [ | |||
{ | |||
component: 'foo:test1.js', | |||
textRange: { | |||
startLine: 1, | |||
endLine: 1, | |||
startOffset: 0, | |||
endOffset: 1, | |||
}, | |||
}, | |||
], | |||
}, | |||
{ | |||
locations: [ | |||
{ | |||
component: 'foo:test2.js', | |||
textRange: { | |||
startLine: 20, | |||
endLine: 20, | |||
startOffset: 0, | |||
endOffset: 1, | |||
}, | |||
}, | |||
], | |||
}, | |||
], | |||
}), | |||
snippets: keyBy( | |||
[ | |||
mockSnippetsByComponent( | |||
'test1.js', | |||
'foo', | |||
times(40, (i) => i + 1) | |||
), | |||
mockSnippetsByComponent( | |||
'test2.js', | |||
'foo', | |||
times(40, (i) => i + 1) | |||
), | |||
], | |||
'component.key' | |||
), | |||
}, | |||
{ | |||
issue: mockRawIssue(false, { | |||
key: 'issue11', |
@@ -121,6 +121,13 @@ it('should show warning when not all issues are accessible', async () => { | |||
expect(await screen.findByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument(); | |||
}); | |||
it('should show secondary location even when no message is present', async () => { | |||
renderProjectIssuesApp('project/issues?issues=issue101&open=issue101&id=myproject'); | |||
expect(await screen.findByRole('button', { name: '1 issue.location_x.1' })).toBeInTheDocument(); | |||
expect(screen.getByRole('button', { name: '2 issue.location_x.2' })).toBeInTheDocument(); | |||
}); | |||
it('should interact with flows and locations', async () => { | |||
const user = userEvent.setup(); | |||
renderProjectIssuesApp('project/issues?issues=issue11&open=issue11&id=myproject'); |
@@ -38,7 +38,7 @@ export default class LocationsList extends React.PureComponent<Props> { | |||
const locationComponents = [componentKey, ...locations.map((location) => location.component)]; | |||
const isCrossFile = uniq(locationComponents).length > 1; | |||
if (!locations || locations.length === 0 || locations.every((location) => !location.msg)) { | |||
if (!locations || locations.length === 0) { | |||
return null; | |||
} | |||
@@ -19,6 +19,7 @@ | |||
*/ | |||
import classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import { translateWithParameters } from '../../helpers/l10n'; | |||
import { MessageFormatting } from '../../types/issues'; | |||
import LocationIndex from '../common/LocationIndex'; | |||
import LocationMessage from '../common/LocationMessage'; | |||
@@ -77,7 +78,11 @@ export default class SingleFileLocationNavigator extends React.PureComponent<Pro | |||
> | |||
<LocationIndex>{index + 1}</LocationIndex> | |||
<LocationMessage> | |||
{<IssueMessageHighlighting message={message} messageFormattings={messageFormattings} />} | |||
{message ? ( | |||
<IssueMessageHighlighting message={message} messageFormattings={messageFormattings} /> | |||
) : ( | |||
translateWithParameters('issue.location_x', index + 1) | |||
)} | |||
</LocationMessage> | |||
</ButtonPlain> | |||
); |
@@ -1,39 +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 SingleFileLocationNavigator from '../SingleFileLocationNavigator'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot('index 1'); | |||
expect(shallowRender({ index: 1 })).toMatchSnapshot('index 2'); | |||
}); | |||
function shallowRender(props: Partial<SingleFileLocationNavigator['props']> = {}) { | |||
return shallow( | |||
<SingleFileLocationNavigator | |||
index={0} | |||
message="" | |||
onClick={jest.fn()} | |||
selected={true} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,41 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly: index 1 1`] = ` | |||
<ButtonPlain | |||
aria-current="location" | |||
className="locations-navigator selected" | |||
innerRef={[Function]} | |||
onClick={[Function]} | |||
preventDefault={true} | |||
stopPropagation={true} | |||
> | |||
<LocationIndex> | |||
1 | |||
</LocationIndex> | |||
<LocationMessage> | |||
<IssueMessageHighlighting | |||
message="" | |||
/> | |||
</LocationMessage> | |||
</ButtonPlain> | |||
`; | |||
exports[`should render correctly: index 2 1`] = ` | |||
<ButtonPlain | |||
aria-current="location" | |||
className="locations-navigator selected" | |||
innerRef={[Function]} | |||
onClick={[Function]} | |||
preventDefault={true} | |||
stopPropagation={true} | |||
> | |||
<LocationIndex> | |||
2 | |||
</LocationIndex> | |||
<LocationMessage> | |||
<IssueMessageHighlighting | |||
message="" | |||
/> | |||
</LocationMessage> | |||
</ButtonPlain> | |||
`; |
@@ -878,6 +878,7 @@ issue.transition.resetastoreview.description=The Security Hotspot should be anal | |||
issue.tabs.code=Where is the issue? | |||
issue.x_data_flows={0} data flow(s) | |||
issue.execution_flow=Full execution flow | |||
issue.location_x=Location {0} | |||
issue.closed.file_level=This issue is {status}. It was detected in the file below and is no longer being detected. | |||
issue.closed.project_level=This issue is {status}. It was detected in the project below and is no longer being detected. | |||