@@ -122,6 +122,7 @@ export default class IssuesServiceMock { | |||
locations: [ | |||
{ | |||
component: 'project:file.foo', | |||
msg: 'location 1', | |||
textRange: { | |||
startLine: 1, | |||
endLine: 1, | |||
@@ -135,6 +136,7 @@ export default class IssuesServiceMock { | |||
locations: [ | |||
{ | |||
component: 'project:file.bar', | |||
msg: 'location 2', | |||
textRange: { | |||
startLine: 20, | |||
endLine: 20, |
@@ -402,6 +402,49 @@ it('should open the actions popup using keyboard shortcut', async () => { | |||
expect(screen.getByRole('searchbox', { name: 'search_verb' })).toBeInTheDocument(); | |||
}); | |||
it('should show code tabs when any secondary location is selected', async () => { | |||
const user = userEvent.setup(); | |||
renderIssueApp(); | |||
await user.click(await screen.findByRole('region', { name: 'Fix this' })); | |||
expect(screen.getByText('location 1')).toBeInTheDocument(); | |||
expect(screen.getByText('location 2')).toBeInTheDocument(); | |||
// Select the "why is this an issue" tab | |||
await user.click( | |||
screen.getByRole('button', { name: 'coding_rules.description_section.title.root_cause' }) | |||
); | |||
expect( | |||
screen.queryByRole('row', { | |||
name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;' | |||
}) | |||
).not.toBeInTheDocument(); | |||
await user.click(screen.getByRole('link', { name: '1 location 1' })); | |||
expect( | |||
screen.getByRole('row', { | |||
name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;' | |||
}) | |||
).toBeInTheDocument(); | |||
// selecting the same selected hotspot location should also navigate back to code page | |||
await user.click( | |||
screen.getByRole('button', { name: 'coding_rules.description_section.title.root_cause' }) | |||
); | |||
expect( | |||
screen.queryByRole('row', { | |||
name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;' | |||
}) | |||
).not.toBeInTheDocument(); | |||
await user.click(screen.getByRole('link', { name: '1 location 1' })); | |||
expect( | |||
screen.getByRole('row', { | |||
name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;' | |||
}) | |||
).toBeInTheDocument(); | |||
}); | |||
describe('redirects', () => { | |||
it('should work for hotspots', () => { | |||
renderProjectIssuesApp(`project/issues?types=${IssueType.SecurityHotspot}`); |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { mockIssue } from '../../../helpers/testMocks'; | |||
import { enableLocationsNavigator, selectFlow, selectLocation } from '../actions'; | |||
import { enableLocationsNavigator, selectFlow } from '../actions'; | |||
import { State } from '../components/IssuesApp'; | |||
describe('selectFlow', () => { | |||
@@ -97,23 +97,3 @@ describe('enableLocationsNavigator', () => { | |||
expect(enableLocationsNavigator({} as State)).toBeNull(); | |||
}); | |||
}); | |||
describe('selectLocation', () => { | |||
it('should select location and enable locations navigator', () => { | |||
expect(selectLocation(5)({ openIssue: mockIssue() })).toEqual({ | |||
locationsNavigator: true, | |||
selectedLocationIndex: 5 | |||
}); | |||
}); | |||
it('should deselect location when clicked again', () => { | |||
expect(selectLocation(5)({ openIssue: mockIssue(), selectedLocationIndex: 5 })).toEqual({ | |||
locationsNavigator: false, | |||
selectedLocationIndex: undefined | |||
}); | |||
}); | |||
it('should ignore if no open issue', () => { | |||
expect(selectLocation(5)({ openIssue: undefined })).toBeNull(); | |||
}); | |||
}); |
@@ -40,24 +40,6 @@ export function disableLocationsNavigator() { | |||
return { locationsNavigator: false }; | |||
} | |||
export function selectLocation(nextIndex: number) { | |||
return (state: Pick<State, 'selectedLocationIndex' | 'openIssue'>) => { | |||
const { selectedLocationIndex: index, openIssue } = state; | |||
if (openIssue) { | |||
if (index === nextIndex) { | |||
// disable locations when selecting (clicking) the same location | |||
return { | |||
locationsNavigator: false, | |||
selectedLocationIndex: undefined | |||
}; | |||
} else { | |||
return { locationsNavigator: true, selectedLocationIndex: nextIndex }; | |||
} | |||
} | |||
return null; | |||
}; | |||
} | |||
export function selectNextLocation( | |||
state: Pick<State, 'selectedFlowIndex' | 'selectedLocationIndex' | 'openIssue'> | |||
) { |
@@ -797,7 +797,19 @@ export class App extends React.PureComponent<Props, State> { | |||
}; | |||
selectLocation = (index: number) => { | |||
this.setState(actions.selectLocation(index)); | |||
const { selectedLocationIndex } = this.state; | |||
if (index === selectedLocationIndex) { | |||
this.setState({ selectedLocationIndex: undefined }, () => { | |||
this.setState({ selectedLocationIndex: index }); | |||
}); | |||
} else { | |||
this.setState(({ openIssue }) => { | |||
if (openIssue) { | |||
return { locationsNavigator: true, selectedLocationIndex: index }; | |||
} | |||
return null; | |||
}); | |||
} | |||
}; | |||
selectNextLocation = () => { |
@@ -41,6 +41,16 @@ export default class LineCode extends React.PureComponent<React.PropsWithChildre | |||
activeMarkerNode?: HTMLElement | null; | |||
symbols?: NodeListOf<HTMLElement>; | |||
componentDidMount() { | |||
if (this.activeMarkerNode) { | |||
this.activeMarkerNode.scrollIntoView({ | |||
behavior: 'smooth', | |||
block: 'center', | |||
inline: 'center' | |||
}); | |||
} | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if ( | |||
this.props.highlightedLocationMessage && |
@@ -94,7 +94,12 @@ export class TabViewer extends React.PureComponent<TabViewerProps, State> { | |||
prevProps.codeTabContent !== codeTabContent || | |||
prevProps.currentUser !== currentUser | |||
) { | |||
this.setState(pState => this.computeState(pState, prevProps.ruleDetails !== ruleDetails)); | |||
this.setState(pState => | |||
this.computeState( | |||
pState, | |||
prevProps.ruleDetails !== ruleDetails || prevProps.codeTabContent !== codeTabContent | |||
) | |||
); | |||
} | |||
if (selectedTab?.key === TabKeys.MoreInfo) { |