diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2019-03-15 10:38:59 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2019-03-26 13:37:56 +0100 |
commit | 51cfe7eed3fa9e659f5e9e98d3e3ba9b9f0a6699 (patch) | |
tree | dcaa6ae58725241dfa09242a7d95b293e33b76f1 | |
parent | 62a8287557d8ea46c6d09a9acd679f1e3c5f01cd (diff) | |
download | sonarqube-51cfe7eed3fa9e659f5e9e98d3e3ba9b9f0a6699.tar.gz sonarqube-51cfe7eed3fa9e659f5e9e98d3e3ba9b9f0a6699.zip |
SONAR-11681 Add loader for issue list
6 files changed, 286 insertions, 9 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts new file mode 100644 index 00000000000..5b3528a60b2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/__tests__/utils-test.ts @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 { scrollToElement } from '../../../helpers/scrolling'; +import { scrollToIssue } from '../utils'; + +jest.mock('../../../helpers/scrolling', () => ({ + scrollToElement: jest.fn() +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('scrollToIssue', () => { + it('should scroll to the issue', () => { + document.querySelector = jest.fn(() => ({})); + + scrollToIssue('issue1', false); + expect(scrollToElement).toHaveBeenCalled(); + }); + it("should ignore issue if it doesn't exist", () => { + document.querySelector = jest.fn(() => null); + + scrollToIssue('issue1', false); + expect(scrollToElement).not.toHaveBeenCalled(); + }); + it('should scroll smoothly by default', () => { + document.querySelector = jest.fn(() => ({})); + + scrollToIssue('issue1'); + expect(scrollToElement).toHaveBeenCalledWith( + {}, + { + bottomOffset: 100, + smooth: true, + topOffset: 250 + } + ); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.tsx b/server/sonar-web/src/main/js/apps/issues/components/App.tsx index 6219650e84a..632211d2af7 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/App.tsx @@ -50,7 +50,8 @@ import { saveMyIssues, serializeQuery, STANDARDS, - ReferencedRule + ReferencedRule, + scrollToIssue } from '../utils'; import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import { Alert } from '../../../components/ui/Alert'; @@ -78,7 +79,6 @@ import { removeSideBarClass, removeWhitePageClass } from '../../../helpers/pages'; -import { scrollToElement } from '../../../helpers/scrolling'; import { isSonarCloud } from '../../../helpers/system'; import { withRouter, Location, Router } from '../../../components/hoc/withRouter'; import '../../../components/search-navigator.css'; @@ -381,7 +381,6 @@ export class App extends React.PureComponent<Props, State> { open: undefined } }); - this.scrollToSelectedIssue(false); } }; @@ -395,10 +394,7 @@ export class App extends React.PureComponent<Props, State> { scrollToSelectedIssue = (smooth = true) => { const { selected } = this.state; if (selected) { - const element = document.querySelector(`[data-issue="${selected}"]`); - if (element) { - scrollToElement(element, { topOffset: 250, bottomOffset: 100, smooth }); - } + scrollToIssue(selected, smooth); } }; diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx index da62b8a436c..66fec76a000 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx @@ -19,7 +19,7 @@ */ import * as React from 'react'; import ListItem from './ListItem'; -import { Query } from '../utils'; +import { Query, scrollToIssue } from '../utils'; interface Props { branchLike: T.BranchLike | undefined; @@ -36,9 +36,38 @@ interface Props { selectedIssue: T.Issue | undefined; } -export default class IssuesList extends React.PureComponent<Props> { +interface State { + prerender: boolean; +} + +export default class IssuesList extends React.PureComponent<Props, State> { + state: State = { + prerender: true + }; + + componentDidMount() { + // ! \\ This prerender state variable is to enable the page to be displayed + // immediately, displaying a loader before attempting to render the + // list of issues. See https://jira.sonarsource.com/browse/SONAR-11681 + setTimeout(() => { + this.setState({ prerender: false }); + if (this.props.selectedIssue) { + scrollToIssue(this.props.selectedIssue.key, false); + } + }, 42); + } + render() { const { branchLike, checked, component, issues, openPopup, selectedIssue } = this.props; + const { prerender } = this.state; + + if (prerender) { + return ( + <div> + <i className="spinner" /> + </div> + ); + } return ( <div> diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx new file mode 100644 index 00000000000..556824173d2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx @@ -0,0 +1,54 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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 * as React from 'react'; +import { shallow } from 'enzyme'; +import IssuesList from '../IssuesList'; +import { mockIssue } from '../../../../helpers/testMocks'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; + +it('should render correctly', async () => { + jest.useFakeTimers(); + + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); + jest.runAllTimers(); + await waitAndUpdate(wrapper); + expect(wrapper).toMatchSnapshot(); +}); + +function shallowRender(overrides: Partial<IssuesList['props']> = {}) { + return shallow( + <IssuesList + branchLike={undefined} + checked={[]} + component={undefined} + issues={[mockIssue(), mockIssue(false, { key: 'AVsae-CQS-9G3txfbFN3' })]} + onFilterChange={jest.fn()} + onIssueChange={jest.fn()} + onIssueCheck={jest.fn()} + onIssueClick={jest.fn()} + onPopupToggle={jest.fn()} + openPopup={undefined} + organization={undefined} + selectedIssue={undefined} + {...overrides} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap new file mode 100644 index 00000000000..c7a30dd2379 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/IssuesList-test.tsx.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +<div> + <i + className="spinner" + /> +</div> +`; + +exports[`should render correctly 2`] = ` +<div> + <ListItem + checked={false} + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + key="AVsae-CQS-9G3txfbFN2" + onChange={[MockFunction]} + onCheck={[MockFunction]} + onClick={[MockFunction]} + onFilterChange={[MockFunction]} + onPopupToggle={[MockFunction]} + selected={false} + /> + <ListItem + checked={false} + issue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN3", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + key="AVsae-CQS-9G3txfbFN3" + onChange={[MockFunction]} + onCheck={[MockFunction]} + onClick={[MockFunction]} + onFilterChange={[MockFunction]} + onPopupToggle={[MockFunction]} + previousIssue={ + Object { + "actions": Array [], + "component": "main.js", + "componentLongName": "main.js", + "componentQualifier": "FIL", + "componentUuid": "foo1234", + "creationDate": "2017-03-01T09:36:01+0100", + "flows": Array [], + "fromHotspot": false, + "key": "AVsae-CQS-9G3txfbFN2", + "line": 25, + "message": "Reduce the number of conditional operators (4) used in the expression", + "organization": "myorg", + "project": "myproject", + "projectKey": "foo", + "projectName": "Foo", + "projectOrganization": "org", + "rule": "javascript:S1067", + "ruleName": "foo", + "secondaryLocations": Array [], + "severity": "MAJOR", + "status": "OPEN", + "textRange": Object { + "endLine": 26, + "endOffset": 15, + "startLine": 25, + "startOffset": 0, + }, + "transitions": Array [], + "type": "BUG", + } + } + selected={false} + /> +</div> +`; diff --git a/server/sonar-web/src/main/js/apps/issues/utils.ts b/server/sonar-web/src/main/js/apps/issues/utils.ts index a64a653fa86..1f5e3e31e03 100644 --- a/server/sonar-web/src/main/js/apps/issues/utils.ts +++ b/server/sonar-web/src/main/js/apps/issues/utils.ts @@ -33,6 +33,7 @@ import { serializeDateShort, RawQuery } from '../../helpers/query'; +import { scrollToElement } from '../../helpers/scrolling'; export interface Query { assigned: boolean; @@ -267,3 +268,10 @@ export function allLocationsEmpty( ) { return getLocations(issue, selectedFlowIndex).every(location => !location.msg); } + +export function scrollToIssue(issue: string, smooth = true) { + const element = document.querySelector(`[data-issue="${issue}"]`); + if (element) { + scrollToElement(element, { topOffset: 250, bottomOffset: 100, smooth }); + } +} |