]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18544 Issues list does not scroll to open issue box on page refresh on Chromiu...
authorstanislavh <stanislav.honcharov@sonarsource.com>
Tue, 21 Mar 2023 08:55:08 +0000 (09:55 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 21 Mar 2023 20:02:50 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssue.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssueBox.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/ConciseIssuesList.tsx
server/sonar-web/src/main/js/apps/issues/conciseIssuesList/__tests__/ConciseIssues-it.tsx

index 8507717caa227c9ee5b3482367fd172a8b90f0cb..7999699162fd61057331e365a3eba613bc466034 100644 (file)
@@ -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 = {
index 2475242d56be4cfd6e833117b8e166405c1ae503..d59b61a0a2c2820bbfb219440f50c0eef6658844 100644 (file)
@@ -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<HTMLLIElement>(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) {
           <ConciseIssueComponent path={issue.componentLongName} />
         </li>
       )}
-      <li>
+      <li ref={element}>
         <ConciseIssueBox
           issue={issue}
           onClick={props.onSelect}
           onFlowSelect={props.onFlowSelect}
           onLocationSelect={props.onLocationSelect}
-          scroll={props.scroll}
           selected={selected}
           selectedFlowIndex={selected ? selectedFlowIndex : undefined}
           selectedLocationIndex={selected ? selectedLocationIndex : undefined}
index e41bef46e560a62be305860841da0016cbfb6943..dc89b45ef5e63224f5e87b02dd91600295b9f5d0 100644 (file)
@@ -34,92 +34,68 @@ interface Props {
   onClick: (issueKey: string) => 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<Props> {
-  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 (
-      <div
-        className={classNames('concise-issue-box', 'clearfix', { selected })}
-        onClick={selected ? undefined : this.handleClick}
-      >
-        <ButtonPlain
-          className="concise-issue-box-message"
-          aria-current={selected}
-          innerRef={(node) => (this.messageElement = node)}
-        >
-          <IssueMessageHighlighting
-            message={issue.message}
-            messageFormattings={issue.messageFormattings}
+  return (
+    <div
+      className={classNames('concise-issue-box', 'clearfix', { selected })}
+      onClick={selected ? undefined : handleClick}
+    >
+      <ButtonPlain className="concise-issue-box-message" aria-current={selected}>
+        <IssueMessageHighlighting
+          message={issue.message}
+          messageFormattings={issue.messageFormattings}
+        />
+      </ButtonPlain>
+      <div className="concise-issue-box-attributes">
+        <TypeHelper className="display-block little-spacer-right" type={issue.type} />
+        {issue.flowsWithType.length > 0 ? (
+          <span className="concise-issue-box-flow-indicator muted">
+            {translateWithParameters(
+              'issue.x_data_flows',
+              issue.flowsWithType.filter((f) => f.type === FlowType.DATA).length
+            )}
+          </span>
+        ) : (
+          <ConciseIssueLocations
+            issue={issue}
+            onFlowSelect={props.onFlowSelect}
+            selectedFlowIndex={selectedFlowIndex}
           />
-        </ButtonPlain>
-        <div className="concise-issue-box-attributes">
-          <TypeHelper className="display-block little-spacer-right" type={issue.type} />
-          {issue.flowsWithType.length > 0 ? (
-            <span className="concise-issue-box-flow-indicator muted">
-              {translateWithParameters(
-                'issue.x_data_flows',
-                issue.flowsWithType.filter((f) => f.type === FlowType.DATA).length
-              )}
-            </span>
-          ) : (
-            <ConciseIssueLocations
-              issue={issue}
-              onFlowSelect={this.props.onFlowSelect}
-              selectedFlowIndex={selectedFlowIndex}
-            />
-          )}
-        </div>
-        {selected &&
-          (issue.flowsWithType.length > 0 ? (
-            <FlowsList
-              flows={issue.flowsWithType}
-              onLocationSelect={this.props.onLocationSelect}
-              onFlowSelect={this.props.onFlowSelect}
-              selectedLocationIndex={selectedLocationIndex}
-              selectedFlowIndex={selectedFlowIndex}
-            />
-          ) : (
-            <LocationsList
-              locations={locations}
-              componentKey={issue.component}
-              onLocationSelect={this.props.onLocationSelect}
-              selectedLocationIndex={selectedLocationIndex}
-            />
-          ))}
+        )}
       </div>
-    );
-  }
+      {selected &&
+        (issue.flowsWithType.length > 0 ? (
+          <FlowsList
+            flows={issue.flowsWithType}
+            onLocationSelect={props.onLocationSelect}
+            onFlowSelect={props.onFlowSelect}
+            selectedLocationIndex={selectedLocationIndex}
+            selectedFlowIndex={selectedFlowIndex}
+          />
+        ) : (
+          <LocationsList
+            locations={locations}
+            componentKey={issue.component}
+            onLocationSelect={props.onLocationSelect}
+            selectedLocationIndex={selectedLocationIndex}
+          />
+        ))}
+    </div>
+  );
 }
index fae19d1641a1220221f5882223294822971a9442..831f2c8f2398d23451812dc1d7e49aac491a3bb8 100644 (file)
@@ -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 (
     <ul>
       {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}
index 0b1b38baf7150b052cd11053c85d4429809ff0f4..d97e7f49f6cac688830d3e892297f703c9f17513 100644 (file)
@@ -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 () => {