From cc07008aa1e0556954639def86f18e551263911b Mon Sep 17 00:00:00 2001 From: stanislavh Date: Tue, 21 Mar 2023 09:55:08 +0100 Subject: [PATCH] SONAR-18544 Issues list does not scroll to open issue box on page refresh on Chromium based --- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 2 +- .../issues/conciseIssuesList/ConciseIssue.tsx | 17 ++- .../conciseIssuesList/ConciseIssueBox.tsx | 130 +++++++----------- .../conciseIssuesList/ConciseIssuesList.tsx | 11 -- .../__tests__/ConciseIssues-it.tsx | 22 +-- 5 files changed, 81 insertions(+), 101 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx index 8507717caa2..7999699162f 100644 --- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx @@ -48,7 +48,7 @@ beforeEach(() => { issuesHandler.reset(); componentsHandler.reset(); window.scrollTo = jest.fn(); - window.HTMLElement.prototype.scrollIntoView = jest.fn(); + window.HTMLElement.prototype.scrollTo = jest.fn(); }); const ui = { diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx index 2475242d56b..d59b61a0a2c 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx @@ -22,13 +22,14 @@ import { Issue } from '../../../types/types'; import ConciseIssueBox from './ConciseIssueBox'; import ConciseIssueComponent from './ConciseIssueComponent'; +const HALF_DIVIDER = 2; + export interface ConciseIssueProps { issue: Issue; onFlowSelect: (index?: number) => void; onLocationSelect: (index: number) => void; onSelect: (issueKey: string) => void; previousIssue: Issue | undefined; - scroll: (element: Element) => void; selected: boolean; selectedFlowIndex: number | undefined; selectedLocationIndex: number | undefined; @@ -36,9 +37,20 @@ export interface ConciseIssueProps { export default function ConciseIssue(props: ConciseIssueProps) { const { issue, previousIssue, selected, selectedFlowIndex, selectedLocationIndex } = props; + const element = React.useRef(null); const displayComponent = !previousIssue || previousIssue.component !== issue.component; + React.useEffect(() => { + if (selected && element.current) { + const parent = document.querySelector('.layout-page-side') as HTMLMenuElement; + const rect = parent.getBoundingClientRect(); + const offset = + element.current.offsetTop - rect.height / HALF_DIVIDER + rect.top / HALF_DIVIDER; + parent.scrollTo({ top: offset, behavior: 'smooth' }); + } + }, [selected]); + return ( <> {displayComponent && ( @@ -46,13 +58,12 @@ export default function ConciseIssue(props: ConciseIssueProps) { )} -
  • +
  • void; onFlowSelect: (index?: number) => void; onLocationSelect: (index: number) => void; - scroll: (element: Element) => void; selected: boolean; selectedFlowIndex: number | undefined; selectedLocationIndex: number | undefined; } -export default class ConciseIssueBox extends React.PureComponent { - messageElement?: HTMLElement | null; +export default function ConciseIssueBox(props: Props) { + const { issue, selected, selectedFlowIndex, selectedLocationIndex } = props; - componentDidMount() { - if (this.props.selected) { - this.handleScroll(); - } - } - - componentDidUpdate(prevProps: Props) { - if (this.props.selected && prevProps.selected !== this.props.selected) { - this.handleScroll(); - } - } - - handleClick = () => { - this.props.onClick(this.props.issue.key); - }; - - handleScroll = () => { - if (this.messageElement) { - this.props.scroll(this.messageElement); - } + const handleClick = () => { + props.onClick(issue.key); }; - render() { - const { issue, selected, selectedFlowIndex, selectedLocationIndex } = this.props; + const locations = React.useMemo( + () => getLocations(issue, selectedFlowIndex), + [issue, selectedFlowIndex] + ); - const locations = getLocations(issue, selectedFlowIndex); - - return ( -
    - (this.messageElement = node)} - > - + + + +
    + + {issue.flowsWithType.length > 0 ? ( + + {translateWithParameters( + 'issue.x_data_flows', + issue.flowsWithType.filter((f) => f.type === FlowType.DATA).length + )} + + ) : ( + - -
    - - {issue.flowsWithType.length > 0 ? ( - - {translateWithParameters( - 'issue.x_data_flows', - issue.flowsWithType.filter((f) => f.type === FlowType.DATA).length - )} - - ) : ( - - )} -
    - {selected && - (issue.flowsWithType.length > 0 ? ( - - ) : ( - - ))} + )}
    - ); - } + {selected && + (issue.flowsWithType.length > 0 ? ( + + ) : ( + + ))} +
    + ); } diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx index fae19d1641a..831f2c8f239 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx @@ -34,16 +34,6 @@ export interface ConciseIssuesListProps { export default function ConciseIssuesList(props: ConciseIssuesListProps) { const { issues, selected, selectedFlowIndex, selectedLocationIndex } = props; - const handleScroll = React.useCallback((element: Element) => { - if (element) { - element.scrollIntoView({ - block: 'center', - behavior: 'smooth', - inline: 'center', - }); - } - }, []); - return (
      {issues.map((issue, index) => ( @@ -54,7 +44,6 @@ export default function ConciseIssuesList(props: ConciseIssuesListProps) { onLocationSelect={props.onLocationSelect} onSelect={props.onIssueSelect} previousIssue={index > 0 ? issues[index - 1] : undefined} - scroll={handleScroll} selected={issue.key === selected} selectedFlowIndex={selectedFlowIndex} selectedLocationIndex={selectedLocationIndex} diff --git a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssues-it.tsx b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssues-it.tsx index 0b1b38baf71..d97e7f49f6c 100644 --- a/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssues-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssues-it.tsx @@ -63,14 +63,20 @@ const issues = [ }), ]; -const originalScrollIntoView = HTMLElement.prototype.scrollIntoView; +const scrollTo = jest.fn(); beforeAll(() => { - HTMLElement.prototype.scrollIntoView = jest.fn(); + // eslint-disable-next-line testing-library/no-node-access + document.querySelector = jest.fn(() => ({ + scrollTo, + getBoundingClientRect: () => ({ + height: 10, + }), + })); }); -afterAll(() => { - HTMLElement.prototype.scrollIntoView = originalScrollIntoView; +beforeEach(() => { + scrollTo.mockClear(); }); describe('rendering', () => { @@ -93,7 +99,7 @@ describe('rendering', () => { selected: 'issue2', }); - expect(HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(1); + expect(scrollTo).toHaveBeenCalledTimes(1); }); it('should show locations and flows when selected', () => { @@ -173,18 +179,16 @@ describe('interacting', () => { }); it('should scroll selected issue into view', () => { - const scrollIntoView = jest.fn(); - window.HTMLElement.prototype.scrollIntoView = scrollIntoView; const { override } = renderConciseIssues(issues, { selected: 'issue2', }); - expect(scrollIntoView).toHaveBeenCalledTimes(1); + expect(scrollTo).toHaveBeenCalledTimes(1); override(issues, { selected: 'issue4', }); - expect(scrollIntoView).toHaveBeenCalledTimes(2); + expect(scrollTo).toHaveBeenCalledTimes(2); }); it('expand button should work correctly', async () => { -- 2.39.5