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;
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 && (
<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}
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>
+ );
}
}),
];
-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', () => {
selected: 'issue2',
});
- expect(HTMLElement.prototype.scrollIntoView).toHaveBeenCalledTimes(1);
+ expect(scrollTo).toHaveBeenCalledTimes(1);
});
it('should show locations and flows when selected', () => {
});
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 () => {