From b96a80637b2f86bd8436e9aaef83b0c2c704fb31 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Thu, 26 Aug 2021 17:12:12 +0200 Subject: [PATCH] SONAR-13736 Keep search query when browsing code file --- .../components/{AppCode.tsx => CodeApp.tsx} | 4 +- .../main/js/apps/code/components/Search.tsx | 19 ++- .../{AppCode-test.tsx => CodeApp-test.tsx} | 8 +- .../code/components/__tests__/Search-test.tsx | 111 ++++++++++++++++++ ...de-test.tsx.snap => CodeApp-test.tsx.snap} | 0 .../__snapshots__/Search-test.tsx.snap | 20 ++++ .../sonar-web/src/main/js/apps/code/routes.ts | 2 +- .../src/main/js/helpers/mocks/component.ts | 12 +- 8 files changed, 162 insertions(+), 14 deletions(-) rename server/sonar-web/src/main/js/apps/code/components/{AppCode.tsx => CodeApp.tsx} (99%) rename server/sonar-web/src/main/js/apps/code/components/__tests__/{AppCode-test.tsx => CodeApp-test.tsx} (97%) create mode 100644 server/sonar-web/src/main/js/apps/code/components/__tests__/Search-test.tsx rename server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/{AppCode-test.tsx.snap => CodeApp-test.tsx.snap} (100%) create mode 100644 server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/Search-test.tsx.snap 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 similarity index 99% rename from server/sonar-web/src/main/js/apps/code/components/AppCode.tsx rename to 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 { +export class CodeApp extends React.PureComponent { mounted = false; state: State; @@ -374,4 +374,4 @@ const mapDispatchToProps: DispatchToProps = { export default connect( 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: Router; } interface State { @@ -40,7 +41,7 @@ interface State { loading: boolean; } -class Search extends React.PureComponent { +export class Search extends React.PureComponent { mounted = false; state: State = { query: '', @@ -49,11 +50,14 @@ class Search extends React.PureComponent { 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 { 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 { }; 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 similarity index 97% rename from server/sonar-web/src/main/js/apps/code/components/__tests__/AppCode-test.tsx rename to 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 = {}) { - return shallow( - = {}) { + return shallow( + { + 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) { + return shallow( + + ); +} 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 similarity index 100% rename from server/sonar-web/src/main/js/apps/code/components/__tests__/__snapshots__/AppCode-test.tsx.snap rename to 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`] = ` + +`; 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 }; } +export function mockTreeComponent(overrides: Partial): TreeComponent { + return { + key: 'my-key', + qualifier: ComponentQualifier.Project, + name: 'component', + visibility: 'public', + ...overrides + }; +} + export function mockComponentMeasure( file = false, overrides: Partial = {} -- 2.39.5