aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2021-08-26 17:12:12 +0200
committersonartech <sonartech@sonarsource.com>2021-08-30 20:08:20 +0000
commitb96a80637b2f86bd8436e9aaef83b0c2c704fb31 (patch)
tree800a4405f0a563feb1015cb89954d22c185fb62a /server/sonar-web/src/main/js
parent49789f6a622f402e85a72b9b77b9dfa827785a60 (diff)
downloadsonarqube-b96a80637b2f86bd8436e9aaef83b0c2c704fb31.tar.gz
sonarqube-b96a80637b2f86bd8436e9aaef83b0c2c704fb31.zip
SONAR-13736 Keep search query when browsing code file
Diffstat (limited to 'server/sonar-web/src/main/js')
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx (renamed from server/sonar-web/src/main/js/apps/code/components/AppCode.tsx)4
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/Search.tsx19
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx (renamed from server/sonar-web/src/main/js/apps/code/components/__tests__/AppCode-test.tsx)8
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/__tests__/Search-test.tsx111
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap (renamed from server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/AppCode-test.tsx.snap)0
-rw-r--r--server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Search-test.tsx.snap20
-rw-r--r--server/sonar-web/src/main/js/apps/code/routes.ts2
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/component.ts12
8 files changed, 162 insertions, 14 deletions
diff --git a/server/sonar-web/src/main/js/apps/code/components/AppCode.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
index cd2924e5c31..fef599b7fee 100644
--- a/server/sonar-web/src/main/js/apps/code/components/AppCode.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx
@@ -71,7 +71,7 @@ interface State {
total: number;
}
-export class AppCode extends React.PureComponent<Props, State> {
+export class CodeApp extends React.PureComponent<Props, State> {
mounted = false;
state: State;
@@ -374,4 +374,4 @@ const mapDispatchToProps: DispatchToProps = {
export default connect<StateToProps, DispatchToProps, Props>(
mapStateToProps,
mapDispatchToProps
-)(AppCode);
+)(CodeApp);
diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.tsx b/server/sonar-web/src/main/js/apps/code/components/Search.tsx
index 00b51655ab1..a680e771c18 100644
--- a/server/sonar-web/src/main/js/apps/code/components/Search.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/Search.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { omit } from 'lodash';
import * as React from 'react';
import { getTree } from '../../../api/components';
import SearchBox from '../../../components/controls/SearchBox';
@@ -32,7 +33,7 @@ interface Props {
location: Location;
onSearchClear: () => void;
onSearchResults: (results?: T.ComponentMeasure[]) => void;
- router: Pick<Router, 'push'>;
+ router: Router;
}
interface State {
@@ -40,7 +41,7 @@ interface State {
loading: boolean;
}
-class Search extends React.PureComponent<Props, State> {
+export class Search extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
query: '',
@@ -49,11 +50,14 @@ class Search extends React.PureComponent<Props, State> {
componentDidMount() {
this.mounted = true;
+ if (this.props.location.query.search) {
+ this.handleQueryChange(this.props.location.query.search);
+ }
}
- componentWillReceiveProps(nextProps: Props) {
- // if the url has change, reset the current state
- if (nextProps.location !== this.props.location) {
+ componentDidUpdate(nextProps: Props) {
+ // if the component has change, reset the current state
+ if (nextProps.location.query.id !== this.props.location.query.id) {
this.setState({
query: '',
loading: false
@@ -79,8 +83,9 @@ class Search extends React.PureComponent<Props, State> {
handleSearch = (query: string) => {
if (this.mounted) {
- const { branchLike, component } = this.props;
+ const { branchLike, component, router, location } = this.props;
this.setState({ loading: true });
+ router.replace({ pathname: location.pathname, query: { ...location.query, search: query } });
const isPortfolio = ['VW', 'SVW', 'APP'].includes(component.qualifier);
const qualifiers = isPortfolio ? 'SVW,TRK' : 'BRC,UTS,FIL';
@@ -109,8 +114,10 @@ class Search extends React.PureComponent<Props, State> {
};
handleQueryChange = (query: string) => {
+ const { router, location } = this.props;
this.setState({ query });
if (query.length === 0) {
+ router.replace({ pathname: location.pathname, query: omit(location.query, 'search') });
this.props.onSearchClear();
} else {
this.handleSearch(query);
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/AppCode-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx
index 346e5028355..20645e522f2 100644
--- a/server/sonar-web/src/main/js/apps/code/components/__tests__/AppCode-test.tsx
+++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/CodeApp-test.tsx
@@ -25,7 +25,7 @@ import { mockIssue, mockRouter } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component';
import { loadMoreChildren, retrieveComponent } from '../../utils';
-import { AppCode } from '../AppCode';
+import { CodeApp } from '../CodeApp';
jest.mock('../../utils', () => ({
loadMoreChildren: jest.fn().mockResolvedValue({}),
@@ -174,9 +174,9 @@ it('should handle select correctly', () => {
});
});
-function shallowRender(props: Partial<AppCode['props']> = {}) {
- return shallow<AppCode>(
- <AppCode
+function shallowRender(props: Partial<CodeApp['props']> = {}) {
+ return shallow<CodeApp>(
+ <CodeApp
component={{
breadcrumbs: [],
name: 'foo',
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/apps/code/components/__tests__/Search-test.tsx
new file mode 100644
index 00000000000..829fb65f7a4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/Search-test.tsx
@@ -0,0 +1,111 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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 { shallow } from 'enzyme';
+import * as React from 'react';
+import { getTree } from '../../../../api/components';
+import { mockComponent } from '../../../../helpers/mocks/component';
+import { mockLocation, mockRouter } from '../../../../helpers/testMocks';
+import { waitAndUpdate } from '../../../../helpers/testUtils';
+import { Search } from '../Search';
+
+jest.mock('../../../../api/components', () => {
+ const { mockTreeComponent, mockComponent } = jest.requireActual(
+ '../../../../helpers/mocks/component'
+ );
+
+ return {
+ getTree: jest.fn().mockResolvedValue({
+ baseComponent: mockTreeComponent(),
+ components: [mockComponent()],
+ paging: { pageIndex: 0, pageSize: 5, total: 20 }
+ })
+ };
+});
+
+it('should render correcly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+it('should search correct query on mount', async () => {
+ const onSearchResults = jest.fn();
+ const wrapper = shallowRender({
+ location: mockLocation({ query: { id: 'foo', search: 'bar' } }),
+ onSearchResults
+ });
+ await waitAndUpdate(wrapper);
+ expect(getTree).toHaveBeenCalledWith({
+ component: 'my-project',
+ q: 'bar',
+ qualifiers: 'BRC,UTS,FIL',
+ s: 'qualifier,name'
+ });
+ expect(onSearchResults).toHaveBeenCalledWith([
+ {
+ breadcrumbs: [],
+ key: 'my-project',
+ name: 'MyProject',
+ qualifier: 'TRK',
+ qualityGate: { isDefault: true, key: '30', name: 'Sonar way' },
+ qualityProfiles: [{ deleted: false, key: 'my-qp', language: 'ts', name: 'Sonar way' }],
+ tags: []
+ }
+ ]);
+});
+
+it('should handle search correctly', async () => {
+ const router = mockRouter();
+ const onSearchClear = jest.fn();
+ const wrapper = shallowRender({ router, onSearchClear });
+ wrapper.instance().handleQueryChange('foo');
+ await waitAndUpdate(wrapper);
+ expect(router.replace).toHaveBeenCalledWith({
+ pathname: '/path',
+ query: {
+ search: 'foo'
+ }
+ });
+ expect(getTree).toHaveBeenCalledWith({
+ component: 'my-project',
+ q: 'foo',
+ qualifiers: 'BRC,UTS,FIL',
+ s: 'qualifier,name'
+ });
+
+ wrapper.instance().handleQueryChange('');
+ await waitAndUpdate(wrapper);
+ expect(router.replace).toHaveBeenCalledWith({
+ pathname: '/path',
+ query: {}
+ });
+ expect(onSearchClear).toHaveBeenCalledWith();
+});
+
+function shallowRender(props?: Partial<Search['props']>) {
+ return shallow<Search>(
+ <Search
+ component={mockComponent()}
+ location={mockLocation()}
+ onSearchClear={jest.fn()}
+ onSearchResults={jest.fn()}
+ router={mockRouter()}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/AppCode-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap
index 233b60a1e9f..233b60a1e9f 100644
--- a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/AppCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/CodeApp-test.tsx.snap
diff --git a/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Search-test.tsx.snap b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Search-test.tsx.snap
new file mode 100644
index 00000000000..a1d47cde013
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Search-test.tsx.snap
@@ -0,0 +1,20 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render correcly 1`] = `
+<div
+ className="code-search"
+ id="code-search"
+>
+ <SearchBox
+ minLength={3}
+ onChange={[Function]}
+ onKeyDown={[Function]}
+ placeholder="code.search_placeholder"
+ value=""
+ />
+ <DeferredSpinner
+ className="spacer-left"
+ loading={false}
+ />
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/code/routes.ts b/server/sonar-web/src/main/js/apps/code/routes.ts
index f35e7d14861..7a0eac5b17c 100644
--- a/server/sonar-web/src/main/js/apps/code/routes.ts
+++ b/server/sonar-web/src/main/js/apps/code/routes.ts
@@ -21,7 +21,7 @@ import { lazyLoadComponent } from '../../components/lazyLoadComponent';
const routes = [
{
- indexRoute: { component: lazyLoadComponent(() => import('./components/AppCode')) }
+ indexRoute: { component: lazyLoadComponent(() => import('./components/CodeApp')) }
}
];
diff --git a/server/sonar-web/src/main/js/helpers/mocks/component.ts b/server/sonar-web/src/main/js/helpers/mocks/component.ts
index 990ad003a5c..19149a524c2 100644
--- a/server/sonar-web/src/main/js/helpers/mocks/component.ts
+++ b/server/sonar-web/src/main/js/helpers/mocks/component.ts
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { ComponentQualifier } from '../../types/component';
+import { ComponentQualifier, TreeComponent } from '../../types/component';
import { MetricKey } from '../../types/metrics';
import { mockMeasureEnhanced } from '../testMocks';
@@ -41,6 +41,16 @@ export function mockComponent(overrides: Partial<T.Component> = {}): T.Component
};
}
+export function mockTreeComponent(overrides: Partial<TreeComponent>): TreeComponent {
+ return {
+ key: 'my-key',
+ qualifier: ComponentQualifier.Project,
+ name: 'component',
+ visibility: 'public',
+ ...overrides
+ };
+}
+
export function mockComponentMeasure(
file = false,
overrides: Partial<T.ComponentMeasure> = {}